数据库隔离可以在不同程度上减少丢失更新,隔离级别定义为4种:脏读、读/写提交、可重复读、序列化
在解释四种隔离之前插个知识点:第一类丢失更新
时间 | 事务一 | 事务二 | 备注 |
---|---|---|---|
T1 | 余额1000 | 卡里总余额为1000 | |
T2 | 余额1000 | ||
T3 | 淘宝花了200 | ||
T4 | 吃饭用了100 | ||
T5 | 提交事务,余额900 | ||
T6 | 不想买了,事务回滚到T2时刻,余额1000 |
整个过程中事务一用了100,但是在最后面 T6 回滚之后居然还是初始1000,这跟现实不符,就是第一类丢失更新(原因:数据库事务的隔离性,一个事务无法探知其他事务的操作)。
1、脏读(dirty read)
脏读是四种隔离级别中最低级的,其含义就是允许一个事务去读取另一个事务中还未提交的数据,举例说明(空白表示没有操作):
时间 | 事务一 | 事务二 | 备注 |
---|---|---|---|
T1 | 查询余额100元 | 卡里只有100元 | |
T2 | 查询余额100元 | ||
T3 | 淘宝买东西用了20,余额80 | ||
T4 | 淘宝买东西用了30,余额50 | 读取到事务二还未提交的数据,未提交的余额为80 | |
T5 | 提交事务 | 这时卡里余额为50 | |
T6 | 突然不想买,回滚事务 | 这时余额还为50 |
在 T3 时刻,事务二买东西了,所以事务一在 T4 时刻去买东西时,因为用了脏读,会去读事务二在 T3 时刻的数据(余额显示为80),所以事务一在 T5 提交事务之后,卡里余额就只剩50。事务二在 T6 时刻突然不想买,回滚事务后,由于数据库克服了以前的第一类丢失更新,余额还是50,这跟现实不符合,这种就是脏读。
2、读/写提交(read commit)
这是数据库第二个隔离级别,其含义就是一个事务只能去读取另一个事务中已经提交的数据(注意:已经提交,就是提交事务成功后的),下面举例说明:
时间 | 事务一 | 事务二 | 备注 |
---|---|---|---|
T1 | 查询余额100元 | 卡里只有100元 | |
T2 | 查询余额100元 | ||
T3 | 淘宝买东西用了20,余额80 | ||
T4 | 淘宝买东西用了30,余额70 | 事务二还未提交,所以余额为70 | |
T5 | 提交事务 | 这时卡里余额为70 | |
T6 | 突然不想买,回滚事务 | 这时余额还为70 |
T3 时刻因为用了读/写提交,事务二还未提交事务,所以事务一是无法知道事务二的数据的,所以余额显示为70,T6 时刻事务二回滚后显示的是正常数据。它克服了第一个脏读可能导致的数据错误。但是还是有可能出现错误,比如下面这个例子:
时间 | 事务一 | 事务二 | 备注 |
---|---|---|---|
T1 | 查询余额100元 | 卡里只有100元 | |
T2 | 查询余额100元 | ||
T3 | 淘宝买东西用了40,余额60 | ||
T4 | 淘宝买东西用了30,余额70 | 事务二还未提交,所以余额为70 | |
T5 | 继续买50,余额10 | 事务一还未提交,所以余额为10 | |
T6 | 提交事务 | 事务二提交,这时余额为10 | |
T7 | 提交事务,发现余额只剩10,不能买单 | 事务一读取事务二已提交的数据,发现余额不足 |
这里的出现问题是什么呢?对于事务一来说,它并不知道事务二做了什么,但是钱确实只剩10了,对事务一来说余额是不能重复去读的,而是会变化,这种叫做:不可重复读。简单来说就是我没做什么,可是我卡里莫名其妙就少了90,导致我无法买单。
3、可重复读(repeatable read)
数据库为了克服不可重复读提出的新的隔离级别。这个针对的是数据库同一条记录,它会使同一条数据库记录的读/写按照序列化来操作,避免了同一条数据的不一致性。然而数据库有可能需要同时对多条记录进行读/写,这时候也会出现问题,下面举例:
时间 | 事务一 | 事务二 | 备注 |
---|---|---|---|
T1 | 查询消费记录10条,准备打印 | 这是初始状态 | |
T2 | 消费了一笔 | ||
T3 | 提交事务 | ||
T4 | 打印出来发现11条记录 | 最终打印结果 |
在 T4 时刻,事务二发现打印出来了11条,会认为这条应该是不存在的、多余。事务二为什么会质疑那多出的一条记录呢?因为事务二并不知道事务一进行了消费(因为这不是同一条记录,可重复读只针对同一条记录),事务二就会以为这是不存在的。这种叫: 幻读
4、序列化(serializable)
为了解决幻读问题,提出了系列化隔离级别,其含义是让SQL按照顺序读/写的方式操作,能够消除事务并发时产生的数据不一致。
5、总结:
并发事务可能会出现的问题:
- 脏读(Dirty read)
- 丢失修改(Lost to modify)
- 不可重复读(Unrepeatableread)
- 幻读(Phantom read)
隔离级别 | 脏读 | 不可重复读 | 幻读 |
---|---|---|---|
脏读 | √ | √ | √ |
读/写提交 | × | √ | √ |
可重复读 | × | × | √ |
序列化 | × | × | × |
√:会出现这个问题;×:不会出现
6、 注意:
- 数据库隔离级别是按照SQL标准规范,不是Spring 或者Java的规范,Spring和Java只是按照SQL的规范定义而已;
- 不可重复读的重点是修改,比如多次读取一条记录发现其中某些列的值被修改;幻读的重点在于新增或者删除,比如多次读取一条记录发现记录增多或减少了;
- 从脏读到序列化,级别越高,越会压制并发,隔离级别需要根据并发大小和性能决定;
7、关于MySQL:
- MySQL InnoDB 存储引擎的默认支持的隔离级别是:可重复读;
原因在于:InnoDB 存储引擎在可重复读事务隔离级别下使用的是Next-Key Lock 锁算法,可以避免幻读的产生,以完全保证事务的隔离性要求。InnoDB 存储引擎在分布式事务的情况下一般会用到序列化隔离级别。 - 因为隔离级别越低,事务请求的锁越少,所以大部分数据库系统的隔离级别都是:读/写提交;
来源注明:
(1)杨开振、周吉文等的《JavaEE互联网轻量级框架整合开发》
(2)JavaGuide面试突击