前言
什么是事务隔离呢?们知道,关系型数据基本都支持事务,事务具备四个特性,分别是:原子性(Atomicity)、一致性(Consistency)、隔离性(Isolation)、持久性(Durability),也就是传说中的ACID,有了这四个特性,才能保证事务中所有数据的正确性。
其中事务的隔离性就是指多个并发的线程同时操作同一条记录时,每个事务中的操作或数据不能被其他事务干扰。如果没有隔离会怎么样?会产生各种各样的问题,比如修改丢失、脏读、幻读等。
下面我们一起看下事务隔离的演变,假设有两个事务线程同时要操作A记录。
1、修改丢失
事务1读取A=100,加上20后等于120写回数据库,同时事务2读取A=100,减去50后等于70写回数据库。这时事务1的操作就会被事务2覆盖,如下图:
为了解决修改被覆盖的问题,数据库给A加了写锁(排他锁),这样事务1在修改前先获取A的排他锁,等事务提交后再释放锁,如下图:
2、脏读
加了排他锁后,修改丢失的问题得到了解决,虽然写数据的问题通过锁解决了,但读却没有解决。比如在事务1在操作A的过程中,事务2读取A,这时就会发生脏读,如下图:
为了解决脏读的问题,得把读数据也要加个读锁(共享锁),并定了规则:如果加了排他锁,就无法获取共享锁,反之,如果加了共享锁,则无法获取排他锁,且共享锁在读取完就要释放,如下图:
3、不可重复读
脏读的问题通过共享锁解决了,但共享锁的规则是读取完数据后立马释放共享锁,这样效率很高,但会引起一个问题,即在事务1中读取A的值后必须立刻释放锁,正巧事务2这时修改了A的值,然后事务1再读取A的值时发现和刚才读的怎么不一样了?如下图:
为了保证同一个记录在当前事务中的值保持一致(即可重复读),则共享锁也必须在事务结束后才能释放,如下图:
4、幻读
可重复读的问题解决了,现在遇到了一个更诡异的问题:事务1选择账号表中所有性别为女的行,并做了修改,修改完成后再查询下记录发现多了一条,原来是事务2在事务1修改的过程中又添加了一条性别为女的行,为了解决这样问题(幻读),只能将隔离级别再调高,每个事务按顺序执行,即串行化。
5、小结
通过以上整合的演化,我们发现隔离级别越来越强,但并发效率却越来越低,每一种隔离级别其实都是为解决特定的问题,做个小结如下:
读未提交(Read Uncommitted):写数据时加了排他锁,直到事务结束,读数据时不加锁,能避免修改丢失,但会读到没有提交或回滚的数据。
读提交(Read Committed):写数据时加了排他锁,直到事务结束,读数据加上共享锁,读完立即释放,能避免修改丢失、脏读,但会出现不可重复读的问题。
可重复读(Repeatable read):写数据时加了排他锁,直到事务结束,读数据加上共享锁,直到事务结束,能避免修改丢失、脏读、不可重复读,但会出现幻读等问题。
串行化(Serializable):事务排队执行,虽然能解决以上所有问题,但效率太低了。
PS:一般没有数据库用串行化,性能比较低,常用的是已提交读和可重复读。而已提交读和可重复读的实现主要是ReadView,即MVCC(多版本控制),此内容单独一篇文章讲解,敬请关注。