mysql 幻读
ddatsh
InnoDB 引擎通过什么技术保证事务ACID特性
- 持久性通过 redo log (重做日志)
- 原子性通过 undo log(回滚日志)
- 隔离性通过 MVCC(多版本并发控制) 或锁机制
- 一致性通过持久性+原子性+隔离性
- 脏读:读到其他事务未提交的数据
- 不可重复读:前后读取的数据不一致
- 幻读:前后读取的记录数量不一致
InnoDB 默认隔离级别虽然是「可重复读」,但很大程度上避免幻读现象(并不是完全解决),解决方案有两种:
-
针对快照读(普通 select),通过 MVCC 方式解决了幻读,执行第一个查询语句后创建 Read View,在 undo log 版本链找到事务开始时的数据
-
针对当前读(select … for update、update、insert、delete),是通过 next-key lock(记录锁+间隙锁)方式解决了幻读
判断到插入的位置被其他事务 加了 next-key lock,插入意向锁,同时进入等待状态
CREATE DATABASE test;
USE test;
CREATE TABLE t_stu ( id BIGINT PRIMARY KEY auto_increment, NAME VARCHAR ( 32 ), score INT );
insert into t_stu (id,name,score)values (1,'小林',50),(2,'小明',60),(3,'小红',70),(4,'小蓝',80);
幻读场景
一
# 事务 A
begin;
select * from t_stu where id = 5;
事务 B 插入一条 id = 5 的记录,并且提交了事务
begin;
insert into t_stu values(5, '小美', 18);
commit;
事务 A 更新 id = 5 这条记录(事务 A 看不到 id = 5 这条记录,但是去更新了这条记录),然后再次查询 id = 5 的记录,事务 A 就能看到事务 B 插入的纪录了,幻读就是发生在这种违和的场景
update t_stu set name = 'bad' where id = 5;
select * from t_stu where id = 5;
前后两次 select * from t_stu where id = 5; 结果集不一致,发生幻读
可重复读隔离级别下,事务 A 第一次执行普通的 select 语句时生成了一个 ReadView,之后事务 B 向表中新插入了一条 id = 5 的记录并提交。接着,事务 A 对 id = 5 这条记录进行了更新操作,在这个时刻,这条新记录的 trx_id 隐藏列的值就变成了事务 A 的事务 id,之后事务 A 再使用普通 select 语句去查询这条记录时就可以看到这条记录了,于是就发生了幻读
二
- T1 时刻:事务 A 先执行「快照读语句」:select * from t_test where id > 100 得到了 3 条记录
- T2 时刻:事务 B 往插入一个 id= 200 的记录并提交
- T3 时刻:事务 A 再执行「当前读语句」 select * from t_test where id > 100 for update 就会得到 4 条记录,此时也发生了幻读现象
要避免这类特殊场景下发生幻读的现象的话,就是尽量在开启事务之后,马上执行 select … for update 这类当前读的语句,因为它会对记录加 next-key lock,从而避免其他事务插入一条新记录