The isolation level of database transactions
序言
互联网企业最为关注的内容之一。面试必考,工作必用。
互联网应用时刻面对着高并发的环境,如商品库存,时刻都是多个线程共享的数据,这样就会在多线程的环境中扣减商品库存。对于数据库而言,就会出现多个事务同时访问同一记录的情况,这样引起数据出现不一致的情况,便是数据库的丢失更新(Lost Update) 问题。
ACID
老生常谈
- Atomic: 一个事务动作不可再分,要么全成功要么全白给。
- Consistency:数据库符合你自然界法则。
- Isolation: 正在讨论的,数据一致性和性能的抉择。
- Durability:断电后不丢。
详解隔离级别
今天有个东西,叫做第二类丢失更新
,当多个事务并发提交可能会出现这种不一致场景:
原因是因为事务1知不道事务2的操作。
所以为了克服各种并发环境下的问题(上述问题是个典型例子),所以引入不同的隔离级别.
I. 未提交读(Read Uncommitted)
未提交读是最低的隔离级别:
- 含义:允许一个事务读取另外一个事务没有提交的数据。
- 特点:1. 危险的隔离级别,实际开发应用少;2. 并发能力高,适合对数据一致性没要求,但追求高并发的场景。
- 问题:脏读
未提交读的"脏读"现象:
脏读:一个事务A读到事务B的未提交的数据,一旦事务B回滚了,事务A读到的数据就很脏.
II.读写提交(Read committed)
读写提交是第二级隔离级别:
- 含义:一个事务只能读取另外一个事务已经提交的数据,不能读取未提交的数据。
- 特点:1.克服脏读问题;2. 多个事务里的数据在未提交前,多管齐下互不可见。
- 问题:不可重复读
读写提交克服脏读问题:
在 T3 时刻,由于采用了读写提交的隔离级别,因此事务2不能读取到事务1中未提交的库存1, 所以扣减库存的结果依旧为1。然后它提交事务,则库存在T4时刻就变为了1。 T5 时刻,事务1回滚,最后结果库存为1,正确。
读写提交带来的不可重复读问题:
不可重复读:如果事务A 按一定条件搜索,期间事务B删除了符合条件的某一条数据,导致事务A再次读取时数据少了一条。这种情况归为不可重复读。或者说事务A正要修改某个数据,但是事务B提交了会导致事务A不一致的结果,导致事务A提交失败。
III.可重复读
读写提交是第三级隔离级别:
- 含义:克服读写提交中出现的不可重复读的现象,因为在读写提交的时候,可能出现一些值的变化,影响当前事务的执行。
- 特点:1.克服不可重复读问题;2. 开始加锁了,一个事务不被允许读取另一个事务未提交的数据。
- 问题:幻读
可重复读克服不可重复读问题:
T5 时刻,事务2读取到的值为0,这时就已经无法扣减了,显然在读写提交中出现的不可重复读的场景被消除了。
可重复读带来的幻读问题:
幻读:事务A按照一定条件进行数据读取,期间事务B插入了相同搜索条件的新数据,事务A再次按照原先条件进行读取时,发现了事务B新插入的数据称为幻读。
IV. 串行化
最严格的隔离级。
- 含义:所有事务排队。
- 特点:1.克服上述提到的所有问题;2. 所有请求排队,高并发环境下性能极低,高延迟降低用户忠诚度。
- 问题:并发性能低
选择合理的事务隔离级别
总结一下:
- 作为互联网开发人员,在开发高并发业务时需要时刻记住隔离级别可能发生的各种概念和相关的现象,这是数据库事务的核心内容之一,也是互联网企业关注的重要内容之一;
- 追求更高的隔离级别,它能更好地保证了数据的一致性,但是也要付出锁(性能)的代价。而且隔离级别越高,性能就越是下降;
- 在选择隔离级别时,要抉择数据一致性和并发性能,鱼和熊掌不可得兼。
例:一个高并发抢购的场景,如果采用串行化隔离级别,能够有效避免数据的不一致性,但是这样会使得并发的各个线程挂起,因为只有一个线程可以操作数据,这样就会出现大量的线程挂起和恢复,导致系统缓慢。而后续的用户要得到系统响应就需要等待很长的时间,最终因为响应缓慢。
以下是关于选型的建议:
- 现实中一般会以
读写提交
为主,它能够防止脏读,而不能避免不可重复读和幻读。 - 为了克服数据不一致和性能问题,可考虑乐观锁,但是乐观锁自旋CPU开销大,还有ABA问题,即哪怕你今天读的一个数据是A,下次还是A,但不能保证中间是否变成了B.
- 对于隔离级别,不同的数据库的支持也是不一样的。例如,Oracle只能支持读写提交和串行化;MySQL能够支持4种;Oracle默认的隔离级别为读写提交, MySQL则是可重复读,这些需要根据具体数据库来决定。