目录
- 一、事务的隔离级别
- 1.1 事务的隔离性
- 1.2 事务并发执行引发的一致性问题
- 1.2.1 脏写
- 1.2.2 脏读
- 1.2.3 不可重复读
- 1.2.4 幻读
- 1.3 SQL标准中的四种隔离级别
- 1.4 MySQL中支持的4中隔离级别
- 二、MVCC原理
- 2.1 版本链
- 2.2 ReadView
- 2.3 READ COMMITED 每次读取数据前都生成一个ReadView
- 2.4 REPEATABLE 在第一次读取数据时生成一个ReadView
- 2.5 MVCC小结
一、事务的隔离级别
1.1 事务的隔离性
MySQL是一个客户端/服务器架构的软件,对于一个服务器来说,可以有多个客户端与之连接,服务器可以同时处理来自多个客户端的多个事务。
当并发执行的多个事务访问相同的数据,就可能出现"事务一致性"问题,举例,A用户有11元,B用户有2元,A同时向B进行两次转账5元的操作,两次转账操作分别对应事务T1和T2,如果按照下列操作顺序执行,那么就可能引发事务一致性问题
那么如何保证事务的一致性呢?最简单粗暴的方式就是让事务之间串行执行,也就是让事务之间都隔离开,互不干涉,这就是事务的隔离性
但让事务完全的串行会严重的降低系统的吞吐量和资源利用率,仔细发现,引发事务一致性问题的根本原因在于多个事务访问了相同的数据,更合理的做法是,在某个事务访问某个数据时,对其他想要访问该数据的事务进行限制,当该事务提交后,其他事务才能继续访问这个数据
1.2 事务并发执行引发的一致性问题
1.2.1 脏写
一个事务修改了另一个未提交事务修改过的数据,称为脏写
脏写引发的一致性问题,现有两个事务T1和T1,都要对x,y数据进行修改,一致性需求是x和y相等,先有如下操作顺序
1.2.2 脏读
一个事务读取了另一个未提交事务修改过的数据,意味着发生了脏读
脏读引发的事务一致性问题:一致性需求是x和y的值始终相同
对于事务T2来说,读取到的是x为1,y为0,得到了一个不一致的状态
1.2.3 不可重复读
一个事务修改了另一个未提交事务修改过的数据,意味着发生了不可重复读现象
不可重复读引发的一致性问题:
事务T1读到的x为0,y为1,是一个不一致状态。
关于不可重复读更严格的解释是,事务T1读取了x的值,之后T2修改了x的值并提交事务T2,随后T1再次读取x的值,发现两次读取到的x值不同
1.2.4 幻读
一事务根据某个搜索条件查询出一些记录,在该事务未提交时,另一个事务写入了一些符合搜索条件的记录,就意味着发生了幻读现象
另一种对于幻读更严格的解释是,T1先读取符合搜索条件的记录,然后T2写入了符合搜索条件的记录,T1再次根据搜索条件读取,发现两次读取到的记录不一样
1.3 SQL标准中的四种隔离级别
设置隔离级别的目的是,舍弃一部分隔离性换取一部分性能,SQL标准中的4个隔离级别:
1.4 MySQL中支持的4中隔离级别
MySQL中的隔离级别与SQL标准中隔离级别的区别在于,MySQL在REPEATABLE READ隔离级别下,可以很大程度上禁止幻读现象发生。
MySQL中的默认隔离级别是:REPEATABLE READ
设置隔离级别的SQL语句:
set [global | session] transaction isolation level 隔离级别;
二、MVCC原理
2.1 版本链
在InnoDB存储引擎中的表,聚簇索引中的记录都包含两个隐藏列,
trx_id:一个事务都某条聚簇索引记录进行修改时,都会把该事务的事务id赋值给trx_id
roll_pointer:每次对聚簇索引记录进行改动时,都会把旧的版本写入到undo日志中,相当于一个指针,通过它可以找到该记录修改前的信息
现有两个事务id分别为100,200,对同一条记录进行UPDATE操作,操作流程:
形成的版本链:
利用记录的版本链来控制并发事务访问相同记录时的行为,把这种机制叫做MVCC
2.2 ReadView
用阻AD UNCOMMITTED 隔离级别的事务来说,可以读取未提交事务修改的记录,因此直接读取版本链中的最新版本就可以了;对于SERIALIZABLE隔离级别的事务来说,InnoDB中使用加锁的方式访问记录,对于READ COMMITED和REPEATABLE READ隔离级别的事务来说,都必须保证读到的记录是已经提交的事务修改过的记录,因此核心问题就是:需要判断版本链中的哪个版本对于当前事务是可见的,InnoDB给出的解决方案是,生成ReadView,ReadView包含下列四个重要内容:
m_ids:生成ReadView时,当前系统中活跃的事务id列表
min_trx_id:m_ids中的最小事务id
max_trx_id:在生成ReadView时,系统分配个下一个事务的事务id
creator_trx_id:生成该ReadView的事务的事务id
生成了ReadView之后,我们就可以借助它来判断当前版本的记录对当前事务是否可见:
- 如果当前记录的trx_id与creator_trx_id相同,意味着当前事务在访问自己修改过的记录,可以访问
- 如果当前记录的trx_id小于min_trx_id,说明生成该版本记录的事务已经提交了,可以访问
- 如果当前记录的trx_id 大于max_trx_id,说明生成该版本的事务在当前事务生成ReadView之后才开启,所以该版本不可以被当前事务访问
- 如果被访问版本的trx_id 在 min_trx_id 和 max_trx_id之间,则需进一步判断trx_id是否在m_ids列表中,如果在说明事务为提交,该版本不可见,如果不在,说明可见
在MySQL中,READ COMMITED和REPEATABLE READ 隔离级别之间一个非常答的区别就是他们生成ReadView的时机不同
2.3 READ COMMITED 每次读取数据前都生成一个ReadView
例如,现在系统中有两个事务id为100,200的事务正在执行,此时某个表中number为1的记录对应的版本链为:
假设现在有一个使用READ COMMITED隔离级别的新事物开始执行:执行语句为
select语句执行过程如下:
(1)执行select语句时生成一个ReadView,m_ids列表为[100,200],min_trx_id为100,max_trx_id为201,creator_trx_id为0
因为这个新开启的事务只进行了select操作,并没有对记录进行修改,所以系统没有为其分配事务id,默认为0
(2)顺序版本链寻找符合要求的版本,返回的是name为"刘备"的版本
之后,将事务id为100的事务提交
事务 200 事务中更新表 hero number 的记录
然后再到刚才使用 READ COMMITTED隔离级别的事务执行select语句,继续查找number为1 的记录,执行步骤如下:
(1)生成ReadView,m_ids列表[200],min_trx_id为200,max_trx_id为200,creator_trx_id为0
(2)顺序版本链,查找符合要求的版本,最终返回的是"张飞"
2.4 REPEATABLE 在第一次读取数据时生成一个ReadView
假设现在有一个使用 REPEATABLE READ 隔离级别的新事务开始执行:
第一次select操作时:
(1)生成ReadView,执行select语句时生成一个ReadView,m_ids列表为[100,200],min_trx_id为100,max_trx_id为201,creator_trx_id为0
(2)找到符合条件的版本"刘备"
之后将事务id为100的事务提交后,再到事务id为200的事务中更新表中number为1的记录
然后再次执行select:
(1)沿用第一次select时生成的ReadView
(2)最终的返回版本还是"刘备"
总结,在REPEATABLE READ隔离级别下,事务的两次查询结果是一样的,因此可以说在REPEATABLE READ级别下避免了不可重复读的发生,同时也很大程度上避免了幻读的发生
2.5 MVCC小结
所谓的MVCC指的就是在使用READ COMMITED 和 REPEATABLE READ这两种隔离级别的事务在执行select操作时,访问记录版本链的过程,这样可以使不同事务的读-写操作并发执行,从而提升系统性能。READ COMMITED 和 REPEATABLE READ的最大区别在于,生成Read View的时机不同,READ COMMITED 每次执行select前都会生成一个新的ReadView,而REPEATABLE READ只在第一次执行select前生成一个ReadView