- SERIALIZABLE 串行化
- MVCC + Next-Key Lock
幻读:
幻读指的是一个事务在进行一次查询之后发现某个记录不存在,然后会根据这个结果进行下一步操作,此时如果另一个事务成功插入了该记录,那么对于第一个事务而言,其进行下一步操作(比如插入该记录)的时候很可能会报错。从事务使用的角度来看,在检查一条记录不存在之后,其进行插入应该完全没问题的,但是这里却抛出主键冲突的异常。
简单来说:事务A的两次读之间有其他事务写操作,比如事务A统计年龄 > 30,当A两次读数据之间其他事务新添加了记录,所以事务A第二次读取到的数据突然多了一个,仿佛出现了幻觉一般,这就是一种幻读
串行化:
- 事务在读操作时,先加表级别的共享锁,直到事务结束才释放
- 事务在写操作时,先加表级别的排它锁,直到事务结束才释放
串行化锁定了整张表,幻读不存在的!!!
--------------------------------------------------------------------------------------------------------------------------------------------------------------------
多版本并发MVCC:
InnoDB 的 MVCC, 是 通过 在 每 行 记录 后面 保存 两个 隐藏 的 列 来 实现 的。 这 两个 列, 一个 保存 了 行的 创建 时间, 一个 保存 行的 过期 时间( 或 删除 时间)。 当然 存储 的 并不是 实际 的 时间 值, 而是 系统 版 本号( system version number)。 每 开始 一个 新的 事务, 系统 版本 号 都会 自动 递增。 事务 开始时 刻 的 系统 版 本号 会 作为 事务 的 版 本号, 用来 和 查询 到 的 每 行 记录 的 版本 号 进行 比较。
MVCC 只在 REPEATABLE READ 和 READ COMMITTED 两个 隔离 级别 下 工作。
MVCC快照读需满足条件:
- InnoDB 只 查找 版本 早于 当前 事务 版本 的 数据 行( 也就是, 行的 系统 版本 号 小于 或 等于 事务 的 系统 版 本号), 这样 可以 确保 事务 读 取的 行, 要么 是在 事务 开始 前 已经 存在 的, 要么 是 事务 自身 插入 或者 修 改过 的。
- 行的 删除 版本 要么 未定义, 要么 大于 当前 事务 版 本号。 这可 以 确保 事务 读取 到 的 行, 在 事务 开始 之前 未被 删除。
MVCC解决了基于快照读下的幻读,事务 读 取的 行, 要么 是在 事务 开始 前 已经 存在 的, 要么 是 事务 自身 插入 或者 修 改过 的。
并不会读到其他事务的写操作 !!!
但是MVCC无法解决当前读下的幻读。
当前读:
select * from tb where ? lock in share mode;
select * from tb where ? for update;
for update:IX锁(意向排它锁),即在符合条件的rows上都加了排它锁
lock in share mode:是IS锁(意向共享锁),即在符合条件的rows上都加了共享锁
排它锁:X锁、 写锁,事务A对一个资源加了X锁后只有A本身能对该资源进行读和写操作,其他事务对该资源的读和写操作都将被阻塞,直到A释放锁为止
共享锁:S锁、 读锁, 事务A锁定的数据其他事务可以共享读该资源,但不能写,直到事务A释放
InnoDB 默认开启间隙锁, innodb_locks_unsafe_for_binlog参数表示 是否禁用Gap Lock间隙锁,默认值0 ,启动间隙锁
测试时,设置参数为1 禁用间隙锁,演示幻读场景
SHOW VARIABLES LIKE '%innodb_locks_unsafe_for_binlog%'
start transaction;
select * from tb where id>100 for update;
update tb set product_num=product_num-1 where id>100;
由于Mysql Server会针对update和delete操作里面的where条件查找满足条件的记录
(查找的不是快照)然后Innodb引擎会返回的满足条件的加锁记录,
当其他事务进行Insert 操作后,进行一次当前读时,就会读到其他事务 Insert 记录,可以明显的发现其会导致幻读
-------------------------------------------------------------------------------------------------------------------------------------------------------
Next-Key Lock是Gap Lock(间隙锁)和Record Lock(行锁)的结合版,都属于Innodb的锁机制
select * from tb where id>100 for update;
- 主键索引 id 会给 id=100 的记录加上 record行锁
- 索引 id 上会加上 gap 锁,锁住 id(100,+无穷大)这个范围
其他事务对 id>100 范围的记录读和写操作都将被阻塞
插入 id=1000的记录时候会命中索引上加的锁会报出事务异常;
Next-Key Lock会确定一段范围,然后对这个范围加锁,保证A在where的条件下读到的数据是一致的,因为在where这个范围其他事务根本插不了也删不了数据,都被Next-Key Lock锁堵在一边阻塞掉了。
施瓦茨(Baron Schwartz); 扎伊采夫(Peter Zaitsev); 特卡琴科(Vadim Tkachenko). 高性能MySQL(第3版)