数据库事务的隔离级别有4个,由低到高依次为Read uncommitted 、Read committed 、Repeatable read 、Serializable ,这四个级别可以逐个解决脏读 、不可重复读 、幻读这几类问题
因为mysql有自动提交事务的功能,所以先把自动提交关了,
- 关闭自动提交:set autocommit=0 或者 set autocommit = off;
- 关闭自动提交后需要手动开启事务:START TRANSACTION
- 手动提交:COMMIT
一、首先来看下Read uncommitted 即 读未提交:
- 再把事务隔离级别设成Read uncommitted:SET SESSION TRANSACTION ISOLATION LEVEL READ UNCOMMITTED
上面过程直接上图解释:
开启两个会话:
然后这是数据库:
准备工作完成,进入正题:
会话1开启事务1,
会话2开启事务2,
事务1查询第一条数据,
事务2修改第一条数据的money,
事务1再次查询第一条数据,
问题来了,由于事务2并没有执行commit,也就是没有提交,事务1却查到了事务2没提交的数据,这就是脏读
脏读:脏读发生在一个事务A读取了被另一个事务B修改,但是还未提交的数据。
Read uncommitted(读未提交) 不能解决脏读、不可重复读(下文 Read committed(读已提交)的时候演示)、幻读(下文演示)的问题。
二、Read committed 即 读已提交:
- 再把事务隔离级别设成Read committed :SET SESSION TRANSACTION ISOLATION LEVEL READ COMMITTED
解决脏读:
步骤还是同上面的步骤一样,详细过程就不写了,直接上图:
事务1:
事务2:
事务1:
到这里可以看出事务1还是1000,并没有出现脏读,OK,解决了。读已提交顾名思义就是事务2 commit后,事务1才会读到修改后的结果,继续。
事务2:
事务1:
那么还有不可重复读,不可重复读是什么?不可重复读 :在一个事务中,两次查询的结果不一致(针对的update操作)
继续演示不可重复读:
事务1:
事务2:
事务1:
问题来了,事务1第一次查询是1000,第二次查询是900,二次查询不一样,出现了不可重复读。
Read committed(读已提交) :能解决脏读,不能解决不可重复读、幻读(下文演示)的问题。
三、Repeatable read 即 可重复读(mysql默认的隔离级别,oracle默认隔离级别是读已提交):
- 再把事务隔离级别设成Repeatable read :SET SESSION TRANSACTION ISOLATION LEVEL REPEATABLE READ
步骤:
事务1:
事务2:
事务1:
到了这里,还是1000,OK,解决了不可重复读。但是moeny不是到900了吗?我现在对事务1继续做update操作还是用1000还是用事务2更新后的900呢?继续:
事务1:
可以看到,虽然我之前查的money=1000,但是实际上我用的是900进行200相加的,最后变成1100,完全符合逻辑。解决。
因为InooDB在可重复读隔离级别规避了幻读问题,了解详细原因可以去百度,这里我直接改用读已提交测试,步骤如下:
事务1:
事务2:
事务1:
问题来了,本来一开始查出来是2行记录,后面再查1次,莫名其妙的多了一行记录,出现幻觉了,这就是幻读。
幻读:在一个事务中,两次查询的结果不一致(针对的insert、delete操作)
Repeatable read(可重复读) :能解决脏读、不可重复读,不能解决幻读(下文演示)的问题。
四、Serializable 即 串行化:
- 再把事务隔离级别设成Serializable :SET SESSION TRANSACTION ISOLATION LEVEL SERIALIZABLE
步骤:
事务1:
事务2:
由于事务1没有commit,因为串行化的时候默认给select * from test加锁的,共享锁(S锁)没有解锁的情况下是不能加排它锁(X锁)的,所以这句话会一直处于阻塞状态,如图:
直到我们事务1 commit了,事务2才能执行。
事务1:
事务2:
那条阻塞的语句在commit后马上就执行了,现在查一下
事务1:
这样幻读就解决了,除非事务2 commit,不然事务1是不会看见3条记录的。
Serializable(串行化) :能解决脏读、不可重复读、幻读的问题。
隔离级别演示完毕。
留下几个问题思考:
- 为什么可重复读级别时,事务1在事务2更新前后读到的数据会是一样?
- 为什么可重复读可以规避幻读?