一文彻底读懂MySQL事务的四大隔离级别( 五 )


# 事务A,Transaction ID 100begin ;UPDATE account SET balance = 1000WHERE id = 1;commit;复制代码在事务B中,执行更新操作,把id=1的记录balance修改为2000,更新完后,undo 日志链如下:
# 事务B,Transaction ID 200begin ; //开个事务,占坑先UPDATE account SET balance = 2000WHERE id = 1;复制代码 

一文彻底读懂MySQL事务的四大隔离级别

文章插图
 
回到事务C,执行查询2
# 事务C,Transaction ID 300begin ;//查询1:select * fromaccount WHERE id = 1;//查询2:select * fromaccount WHERE id = 1;复制代码查询2:执行分析:
  • 在RR 级别下,执行查询2的时候,因为前面ReadView已经生成过了,所以直接服用之前的ReadView,活跃事务列表为[100,200].
  • 由上图undo日志链可得,最新版本的balance为2000,它的事务ID为200,在活跃事务列表里,所以当前事务(事务C)不可见 。
  • 我们继续找下一个版本,balance为1000这行记录,事务Id为100,也在活跃事务列表里,所以当前事务(事务C)不可见 。
  • 继续找下一个版本,balance为100这行记录,事务Id为50,小于活跃事务ID列表最小记录100,所以这个版本可见,因此,查询2的结果,也是返回balance=100这个记录~~
锁相关概念补充(附):共享锁与排他锁InnoDB 实现了标准的行级锁,包括两种:共享锁(简称 s 锁)、排它锁(简称 x 锁) 。
  • 共享锁(S锁):允许持锁事务读取一行 。
  • 排他锁(X锁):允许持锁事务更新或者删除一行 。
如果事务 T1 持有行 r 的 s 锁,那么另一个事务 T2 请求 r 的锁时,会做如下处理:
  • T2 请求 s 锁立即被允许,结果 T1 T2 都持有 r 行的 s 锁
  • T2 请求 x 锁不能被立即允许
如果 T1 持有 r 的 x 锁,那么 T2 请求 r 的 x、s 锁都不能被立即允许,T2 必须等待T1释放 x 锁才可以,因为X锁与任何的锁都不兼容 。
记录锁(Record Locks)
  • 记录锁是最简单的行锁,仅仅锁住一行 。如:SELECT c1 FROM t WHERE c1 = 10 FOR UPDATE
  • 记录锁永远都是加在索引上的,即使一个表没有索引,InnoDB也会隐式的创建一个索引,并使用这个索引实施记录锁 。
  • 会阻塞其他事务对其插入、更新、删除
记录锁的事务数据(关键词:lock_mode X locks rec but not gap),记录如下:
RECORD LOCKS space id 58 page no 3 n bits 72 index `PRIMARY` of table `test`.`t` trx id 10078 lock_mode X locks rec but not gapRecord lock, heap no 2 PHYSICAL RECORD: n_fields 3; compact format; info bits 0 0: len 4; hex 8000000a; asc;; 1: len 6; hex 00000000274f; asc'O;; 2: len 7; hex b60000019d0110; asc;;复制代码间隙锁(Gap Locks)
  • 间隙锁是一种加在两个索引之间的锁,或者加在第一个索引之前,或最后一个索引之后的间隙 。
  • 使用间隙锁锁住的是一个区间,而不仅仅是这个区间中的每一条数据 。
  • 间隙锁只阻止其他事务插入到间隙中,他们不阻止其他事务在同一个间隙上获得间隙锁,所以 gap x lock 和 gap s lock 有相同的作用 。
Next-Key Locks
  • Next-key锁是记录锁和间隙锁的组合,它指的是加在某条记录以及这条记录前面间隙上的锁 。
RC级别存在幻读分析因为RC是存在幻读问题的,所以我们先切到RC隔离级别,分析一波~
假设account表有4条数据 。
  • 开启事务A,执行当前读,查询id>2的所有记录 。
  • 再开启事务B,插入id=5的一条数据 。
  • 事务B插入数据成功后,再修改id=3的记录
  • 回到事务A,再次执行id>2的当前读查询
 
一文彻底读懂MySQL事务的四大隔离级别

文章插图
 
  • 事务B可以插入id=5的数据,却更新不了id=3的数据,陷入阻塞 。证明事务A在执行当前读的时候在id =3和id=4这两条记录上加了锁,但是并没有对 id > 2 这个范围加锁~
  • 事务B陷入阻塞后,切回事务A执行当前读操作时,死锁出现 。因为事务B在 insert 的时候,会在新纪录(id=5)上加锁,所以事务A再次执行当前读,想获取id> 3 的记录,就需要在 id=3,4,5 这3条记录上加锁,但是 id = 5这条记录已经被事务B 锁住了,于是事务A被事务B阻塞,同时事务B还在等待 事务A释放 id = 3上的锁,最终产生了死锁 。
 
一文彻底读懂MySQL事务的四大隔离级别

文章插图
 
因此,我们可以发现,RC隔离级别下,加锁的select, update, delete等语句,使用的是记录锁,其他事务的插入依然可以执行,因此会存在幻读~


推荐阅读