本文主要通过大量的实例截图,来通俗的讲解MySQL的四种事务隔离级别的效果。关于事务隔离级别的概念以及不同隔离级别会引发的问题,大家可以自行百度,此处不再赘述。
标准数据库的四种事务隔离级别,不同隔离级别会引发的问题:
隔离级别 | 脏读 | 不可重复读 | 幻读 |
Read Uncommitted | Y | Y | Y |
Read Committed | N | Y | Y |
Repeatable Read | N | N | Y |
Serializable | N | N | N |
MySQL采用的默认隔离级别是Repeatable Read,我们可以用set global|session tx_isolation=‘xxx‘和select @@global|session.tx_isolation来修改和查看global或session的隔离级别。下面我们一一介绍不同隔离级别在‘增删改’操作时的效果。
1. Read Uncommitted
当前数据库的隔离级别为Read Uncommitted
①增:
session1开启事务,往company表插入一条数据,成功:
session2开启事务,查询company表,session1插入的未提交数据对session2可见。这就出现了脏读和幻读,因为对于session2来说莫名其妙的多出了一条未提交的数据;而且出现了不可重复读,因为这次读到的记录跟上一次读到的不一样:
如果session2在不知情的情况下,又往数据库insert一条company_id为6的数据;又或者他以为company_id为6的数据是已经commit的数据,要对他进行update或者delete操作。那这些操作都会阻塞,直到session1完成事务为止,因为这条数据还在session1事务的管理中,后面可能还会发生变化:
但如果session2不是修改的session1插入的数据,update、insert和delete是可以成功的,因为InnoDB采用的是行锁:
②删:
session1开启事务,并从company表中删除一条记录:
session2开启事务,查询company表,session1删除后未提交数据对session2可见,典型的脏读。也出现了幻读,因为对于session2来说莫名其妙的少出了一条数据,而且出现了不可重复读,因为这次读到的记录跟上一次读到的不一样:
跟上面一样,如果session2对session1事务中操作的记录进行delete、insert和update会被阻塞,直到session1结束事务并释放行锁。而对其他行的delete、insert和update操作可以成功:
③改:
session1开启事务,并修改company表中的一条记录:
session2开启事务,查询company表,session1修改后未提交数据对session2可见。但没有出现了幻读,因为对于session2来说数据量并没有改变,但出现了脏读和不可重复读,因为这次读到了session1未提交的修改数据而且记录跟上一次读到的不一样:
跟上面一样,如果session2对session1事务中操作的记录进行delete、insert和update会被阻塞,直到session1结束事务并释放行锁。而对其他行的delete、insert和update操作可以成功:
总结:Read Uncommitted隔离级别会出现:脏读、不可重复读和幻读。每个session在事务中的操作会对其他session事务立即可见,哪怕是未提交的操作。但当前session事务正在操作的数据,只要当前事务没有结束或者回滚,就无法被其他session修改,其他session会阻塞。
2. Read Committed
当前数据库的隔离级别为Read Committed
①增:
session1开启事务,往company表插入一条数据,成功并且session1可以看到这条记录:
session2开启事务,查询company表,session1插入的未提交数据对session2不可见。在session1没有提交之前不会出现幻读和不可重复读:
但只要session1一提交就会出现幻读和不可重复读:
如果session2对session1事务中操作的记录进行delete、insert和update会被阻塞,直到session1结束事务并释放行锁。而对其他行的delete、insert和update操作可以成功:
②删:
session1开启事务,并从company表中删除一条记录:
session2开启事务,查询company表,session1删除后未提交数据对session2不可见。当然也没有出现幻读和不可重复读:
但只要session1一提交就会出现幻读和不可重复读:
跟上面一样,如果session2对session1事务中操作的记录进行delete、insert和update会被阻塞,直到session1结束事务并释放行锁。而对其他行的delete、insert和update操作可以成功。
③改:
session1开启事务,并修改company表中的一条记录:
session2开启事务,查询company表,session1修改后未提交数据对session2不可见。没有出现了幻读、脏读和不可重复读:
但只要session1一提交就会出现不可重复读:
跟上面一样,如果session2对session1事务中操作的记录进行delete、insert和update会被阻塞,直到session1结束事务并释放行锁。而对其他行的delete、insert和update操作可以成功。
总结:Read Committed隔离级别会出现:不可重复读和幻读。每个session在事务中的操作对其他session事务是不可见的。当前session事务中正在操作的数据,只要当前事务没有结束或者回滚,就无法被其他session修改,其他session会阻塞。一旦提交就可能会出现不可重复读和幻读。
3.Repeatable Read
当前数据库的隔离级别为Repeatable Read
①增:
session1开启事务,往company表插入一条数据,成功:
session2开启事务,查询company表,session1删除后未提交数据对session2不可见。当然也没有出现幻读和不可重复读:
session1提交后,session2也不会出现不可重复读:
不是说Repeatable Read会出现幻读么,这里为什么没有多出一条记录?其实Repeatable Read隔离级别可以保证每次读取都是一样的记录,幻读只是说如果session2正在insert的数据和被session1插入的数据相同时会报duplicate key的错,因为session1已经插入了。这样让用户就会感到奇怪,为什么明明没有记录却无法插入呢:
②删:
session1开启事务,并从company表中删除一条记录:
session2开启事务,查询company表,session1删除后未提交数据对session2不可见。当然也没有出现幻读和不可重复读:
session1提交后,session2也不会出现不可重复读。但幻读存在,因为如果我们去delete或者update session1已经删掉的记录时,会显示受影响的记录为0;而去insert一个跟删掉的记录同样company_id的记录却可以成功,说明记录实际上已经不存在了:
③改:
session1开启事务,并修改company表中的一条记录:
session2开启事务,查询company表,session1修改后未提交数据对session2不可见。没有出现了幻读、脏读和不可重复读:
session1提交后,session2也不会出现不可重复读。跟上面一样幻读依旧存在,如果我们去根据session1修改后的条件去update或者delete记录时可以成功,说明记录实际上已经被修改了:
总结:Repeatable Read隔离级别会出现:幻读。每个session在事务中的操作对其他session事务是不可见的。当前session事务中正在操作的数据,只要当前事务没有结束或者回滚,就无法被其他session修改,其他session会阻塞。一旦提交就可能会出现幻读。所以Repeatable Read相当于生成一个数据库记录的快照,表面上每次读取的数据都一样吗,但实际上有可能已经发生了变化。
4.Serializable
当前数据库的隔离级别为Serializable
Serializable主要是使用InnoDB的行锁来实现的,增删改都是加行锁,所以下面就只讨论增的情况:
session1开启事务,往company表插入一条数据,成功:
session2开启事务,查询company表时直接被阻塞,查询刚插入的数据时也被阻塞,但查询其他数据时却没问题。说明采用Serializable隔离级别时,利用了InnoDB行锁的特性,谁修改列谁就能获得列的写锁,所以其他session是无法读取和写入的。第一个全表查询包括了刚被session1插入的数据,所以被阻塞:
如果我们现在回到session1里面去修改company_id=1的记录可以成功吗?结果是被阻塞,因为该记录已经被session2读取的同时也被session2加上了读锁。所以session1可以读取,但无法写入:
这种情况下脏读、不可重复读和幻读都不可能出现,因为其他事务修改的记录,当前事务根本没机会读取;而被当前事务读取的数据,其他事务根本没机会修改。
Serializable是一种完全同步机制,所以很容易出现死锁,所以要特别注意,下面我们来看看死锁的例子:
首先session1和session2分别取出company_id为1和2的两条数据,根据上面的例子我们可以知道,session1会给company_id=1的记录加读锁,session2也会给company_id=2的记录加读锁:
然后让session1去更新company_id=2的记录,肯定被阻塞;再让session2区更新company_id=1的记录,这时MySQL就会告诉我们出现了死锁,并强制关闭了session2的事务,让session1更新成功。之所以会出现死锁,因为session1要等待session2释放company_id=2的记录的读锁,而session2又要等待session1释放company_id=1的记录的读锁:
总结:Serializable主要是使用InnoDB的行锁来实现的,在事务中只要select出来的行都会被当前session加上read锁,如果去修改有read锁的行会被升级为write锁,没有read锁的行则直接加write锁。所以Serializable隔离级别不会出现脏读、不可重复读和幻读(第一次读的时候已经给行加read锁,其他session根本无法修改select出来的行,所以就保证了多次读取的一致性)