解析事务隔离(事务隔离是如何解决脏读、幻读、不可重复读等问题)

前言

锁机制和索引是数据库两块极其重要的知识点,也是面试中面试官最爱问的两个部分。前面的文章已经对索引知识有了一个较为全面的解析,这篇文章主要从并发事务的角度出发,解析并发事务带来的问题,以及事务隔离是如何解决并发事务带来的问题。

文章中会涉及到MVCC(多版本并发控制)和数据库的锁机制以及锁协议相关知识,若不是很了解可以翻阅我其它两篇有关这方面的博文。MVCC(多版本并发控制)-----《InnoDB的MVCC实现原理(InnoDB如何实现MVCC以及MVCC的工作机制)》锁机制和锁协议-----《解析数据库锁协议和InnoDB的锁机制》

事务

事务存在意义是保证数据的一致性。事务保证了事务内的一系列操作,要不都执行,要不都不执行。例如A账号给B账户转账10000,则A账号减去10000,B账号增加10000,这两个操作必须是都成功执行或者都得不到执行。

事务的四个特性(ACID)

事务具有原子性、一致性、持久性、隔离性这四个特性,事务通过确保原子性、隔离性、持久性来确保事务的一致性。

原子性(Atomicity)

事务的原子性是指事物是最小的执行单位,不允许分割,事务内的一系列操作,要不都执行,要不都不执行。

一致性(Consistency)

事物的提交只会导致数据库的状态从一个一致性状态到另外一个一致性状态,换句话来说就是事务一系列操作的中间操作导致的中间不一致状态不会让别的事务看到。一致性状态指的是多个事务访问到的数据库的数据和数据结构是一致的。

隔离性(Isolation)

事务的隔离性是指并发事务在执行过程中不能相互影响,其对数据库的影响和它们串行执行时一样。(数据库综合考虑性能和数据一致性,提出了四个隔离级别,可串行化是最高隔离级别)

持久性(Durability)

一个事务被提交之后。它对数据库中数据的改变是持久的,即使数据库发生故障也不会导致提交改变结果的丢失。

事务特性分析

事务具有四个特性,但其实事务存在的意义就是保证事务的数据以及数据结构的一致性和数据完整性。因此四个特性中最重要的其实是一致性,原子性和隔离性的存在都是为了保证事务的一致性。

破坏数据(数据结构)一致性的行为主要是两个方面,一是并发事务的执行,另外一个是事务或者数据库故障。数据库系统是通过并发控制来避免并发事务执行带来的数据不一致性问题,通过日志恢复技术来处理数据库故障带来的数据不一致性问题。并发控制技术保证了事务的隔离性,使数据库的一致性状态不会因为并发执行的操作被破坏。日志恢复技术保证了事务的原子性,使一致性状态不会因事务或系统故障被破坏。同时使已提交的对数据库的修改不会因系统崩溃而丢失,保证了事务的持久性。

并发事务执行带来的问题

脏读

脏读指的是事务读取了另外一个事务未提交的数据,但因为事务回滚,所以该事务读取的是脏数据。当A事务正在访问数据并且对数据进行了修改,而这种修改还没有提交到数据库中,这时B事务也访问了这个未提交的数据,然后使用了这个数据。因为这个数据是还没有提交的数据,A事务有可能会回滚,那么B事务读到的这个数据可能是“脏数据”,而依据“脏数据”所做的操作可能是不正确的。
在这里插入图片描述
事务2在事务1未提交时,读取了余额5000,而事务1出错回滚,因此此时余额应该是10000,但事务2因为已经读取过一次,因此不会在事务1回滚之后再读取余额值,事务2读取到的余额依旧是5000,此时事务2读取的就是脏数据,事务2在此基础上做的修改就是错误的,事务2再存入10000,事实应该是10000+10000=20000,但因为读取的是脏数据,所以存入之后是5000+10000=15000,更新的结果就是错误的。

丢失更新

第一类丢失更新

在没有事务隔离的情况下,两个事务同时更新一行数据,但是第二个事务却中途失败回滚, 导致两个事务对数据的修改都失效了。例如一行数据有A和B两个字段

时间顺序 事务1 事务2
1 开始事务
2 开始事务
3 读取A值30
4 读取B值20
5 A=A-10
6 B=B-20
7 提交事务
8 事务回滚

事务2事务回滚,导致这一行数据都恢复到事务之前的值,导致A和B的值都回滚到初始状态,A值为30,B值为20。事务1的更新丢失。

第二类丢失更新

第二类丢失更新是不可重复读的特例,事务在对数据进行修改的过程中覆盖了其它事务对该数据的修改,导致其它事务修改丢失

时间顺序 事务1 事务2
1 开始事务
2 开始事务
3 读取A值30
4 读取A值30
5 A=A+10
6 A=A-20
7 提交事务
8 提交事务

数据库中最终A的值为10,事务2对A值的修改覆盖了事务1对A值的修改,仿佛事务1没有发生一样。

不可重复读

指在一个事务内多次读同一数据,但在这个事务还没有结束时,另一个事务也访问该数据并且有修改数据行为。那么,在第一个事务中的两次读数据之间,由于第二个事务的修改导致第一个事务两次读取的数据可能不太一样。这就发生了在一个事务内两次读到的数据是不一样的情况,因此称为不可重复读。

时间顺序 事务1 事务2
1 开始事务
2 开始事务
3 读取A值30
4 读取A值30
5 A=A-10
6 读取A值20
7 提交事务
8 提交事务

幻读

幻读与不可重复读类似。它发生在一个事务读取了几行数据,接着另一个并发事务插入了或者删除一些数据时。在随后的查询中,第一个事务就会发现多了一些原本不存在的数据或者少了一些原本存在的数据,就好像发生了幻觉一样,所以称为幻读。

不可重复读和幻读共同点都是事务在多次读数据的过程中,有其它事务对这些数据进行了更新。不同点是不可重复读情况中其它事务是更新了数据的内容,而幻读是增删了一些数据。

隔离级别

上文提到数据库通过并发控制来避免并发事务执行带来的数据不一致性问题,这里的并发控制通过隔离级别来实现的。

SQL 标准定义了四个隔离级别:
(1) READ-UNCOMMITTED(读取未提交): 最低的隔离级别,允许读取尚未提交的数据变更。事务可以在其它事务更改数据时读取数据,但不能在其它事务更改数据时也进行数据更改,即多个事务可以写时读,但不能同时写(该隔离级别采用是一级封锁协议,写前加X锁,读操作不需要加锁,因此可以写时读)。禁止了第一类丢失更新,可能会导致脏读、幻读或不可重复读。
(2)READ-COMMITTED(读取已提交): 允许读取并发事务已经提交的数据,不可同时写,不可写时读(该隔离级别采用MVCC和锁或者二级封锁协议,二级封锁协议-写前加X锁,事务结束才释放,读前加S锁,读完就释放)。可以阻止脏读和第一类丢失更新,但是幻读或不可重复读仍有可能发生。
(3)REPEATABLE-READ(可重复读): 当有事务正在该行数据进行读操作时禁止其它事务对该行的写操作,当有事务对该行数据进行写操作时,禁止其它一切操作。只能共享读,不可读时写,不可写时读写(通过用MVCC和锁或者使用三级封锁协议实现,三级封锁协议-写前加X锁,事务结束才释放,读前加S锁,事务结束就释放)。可重复读级别使得对同一字段的多次读取结果都是一致的,除非数据是被本身事务自己所修改,可以阻止脏读和不可重复读,但幻读仍有可能发生。
(4)SERIALIZABLE(可串行化): 最高的隔离级别,完全服从ACID的隔离级别。所有的事务依次逐个执行,这样事务之间就完全不可能产生干扰,也就是说,该级别可以防止脏读、不可重复读以及幻读。
在这里插入图片描述
隔离级别越高,越能保证数据的完整性和一致性,但是对并发性能的影响也就越大。大多数数据库的默认级别就是Read committed,比如Sql Server 、Oracle。它能够避免脏读取,而且具有较好的并发性能,尽管它会导致不可重复读、幻读和第二类丢失更新这些并发问题,但可在应用程序中使用锁来控制。而Mysql的隔离级别是REPEATABLE-READ(可重复读),它通过与Next-Key Lock 行级锁锁算法的配合可以达到SERIALIZABLE(可串行化)级别的效果,可以禁止幻读。InnoDB使用MVCC来非阻塞的解决了读写之间的冲突

拓展-为什么REPEATABLE-READ隔离级别可以禁止第二类更新丢失问题,而READ-COMMITTED隔离级别不行。

关键点在于REPEATABLE-READ隔离级别使用的是三级封锁协议,而READ-COMMITTED隔离级别使用的是二级封锁协议。三级封锁协议与二级封锁协议的区别就在于三级封锁协议在读数据时需要加S锁,直至事务结束才释放S锁,而二级封锁协议在读数据时虽然也需要加S锁,但读完数据就可以释放S锁。
如下表这个例子,若使用二级封锁协议:事务1读取A值之前加S锁,读完就释放,此时事务2也是读取A值之前加S锁,读完就释放。因此最终事务2的更新还是会覆盖事务1。而使用三级封锁协议:事务1读取A值之前加S锁,读取完之后不会释放,本来是要等事务结束才会释放,但由于还需要再次对该行的A值进行更新,S锁升级为X锁,因此一直到事务1结束,事务2都没有机会读取或者更新数据,自然不会发生第二类更新丢失。

时间顺序 事务1 事务2
1 开始事务
2 开始事务
3 读取A值30
4 读取A值30
5 A=A+10
6 提交事务
7 A=A-20
8 提交事务
发布了25 篇原创文章 · 获赞 62 · 访问量 6504

猜你喜欢

转载自blog.csdn.net/qq_41008202/article/details/105437443