事务隔离的具体原理实现
创建时间:2021年11月12日15:16:58
编辑时间:2021年11月13日19:23:27 (最后一问错误,重新解释了下)
文章目录
——————————————————————————————
要素:串联一致性读、当前读和行锁
事务隔离原理——一致性读视图
问:什么是视图?
答:
一个指代视图虚拟表,调用时执行查询语句并生成结果;
一个指代MVCC的一致性读视图,用于支持读提交和可重复读的实现。
问:介绍下一致性读视图(快照)的生成原理。
答:是基于全库生成的。每个事务会严格按照诞生顺序向系统申请一个递增式事务id,各个事务对数据的操作会产生对应的数据版本(row),并标记该版本的事务id(row trax_id),流程如下例图所示:
(箭头相当于undo log的回滚段)各版本的数据通过回滚段和当前最新数据计算得来。
问:可重复读怎么利用快照实现的?
答:可重复读要求视图内容为启动那一刻所有已提交的事务以及自己后续的操作内容,所以在可重复读事务启动时,会记录当前系统所有处于未提交状态的事务,组成数组,其中最小值为低水位,当前系统已知最大的事务值+1为高水位,组成视图。
举例:全库事务id有1、2、3、4、5、6、7
活跃事务id有[3,4,5],则低水位为3,高水位为8
此时,
低水位以前的数据可见,低水位到高水位之间,处于活跃的事务是不可见的,非活跃的事务可见的;
高水位之后的事务不可见;
——————————————————————
(1、2、3、4、5、6、7)
1、2可见;3、4、5不可见;6、7可见;7之后的事务不可见。
——————————————————————
问:与可重复读相比,读提交是怎么实现的?
答:简单类比,读提交是在每次执行sql前都会计算出新的视图。(PS:对读提交来说,start transaction with consistent snapshot等同start transaction)
问:什么时候清理这些数据版本?
答:(个人未知)
更新——当前读
问:什么是当前读?哪里用到?
答:为了保证MVCC下的事务操作不丢失数据更新,所以更行数据时采用当前读——获取要更新的最新的记录;其根本原因是表结构没有对应的行数据,也没有事务id,但是MySQL的8.0版本已经支持可重复读的逻辑了(DDL不会终止程序)。
此外,对select也可进行当前读操作
-- 共享锁
select k from t where id=1 lock in share mode;
-- 排他锁
select k from t where id=1 for update;
实战题
问:下图事务B查询结果是多少?为什么?
事务B快照时获取的k值是1,但更新时的读是当前读,获取的k值是事务C修改后的值——2,所以k被赋予2+1,此时对于事务B的快照内容来说,k即为3
假如事务C没有立刻提交,而是在事务B的更新语句发起后再提交,那根据二阶段锁协议,此时占据该行数据写锁的事务C还没释放,事务B要进行当前读,需要获取读锁,因此会等待写锁释放。
问:简单说下一致性读、当前读、二阶段锁协议之间的关系。
答:一致性读原理确保可重复读、读提交事务的实现,更新数据时只能是当前读,防止数据更新丢失,但要先遵循二阶段锁协议。
问:有个场景,开启一个事务后,需要对特定数据进行更新,在同一个事务内发现旧值没变化,求原因。
start transaction with consistent snapshot;
update t set k = 3 where k = 2;
SELECT * from t where k = 3;
commit;
答:当事务启动命令为即时性(start transaction with consistent snapshot),则可能在执行本事务第一个update语句之前,存在其他事务对该数据进行了更新,导致本事务的更新判定条件无法命中,由于本事务还没释放便进行了一次查询,得出的结果是数据没变化(启动事务的命令执行时便产生了一致性读视图),所以就产生了更新失效的错觉。
(例子为立即启动事务视图的命令,如果是start tansaction的话,则在执行第一条sql时才会生成视图)
解决思路:
改成开启事务后先检查符合条件的数据条数,然后更新时判定影响条数是否等于查询条数,不等于则结束事务,再发起一次事务。