初始化语句

1
2
3
4
5
6
7
8
9
10
11
12
13
14
mysql> CREATE TABLE `t` (
-> `id` int(11) NOT NULL,
-> `c` int(11) DEFAULT NULL,
-> `d` int(11) DEFAULT NULL,
-> PRIMARY KEY (`id`),
-> KEY `c` (`c`)
-> ) ENGINE=InnoDB;
Query OK, 0 rows affected (0.06 sec)

mysql>
mysql> insert into t values(0,0,0),(5,5,5),
-> (10,10,10),(15,15,15),(20,20,20),(25,25,25);
Query OK, 6 rows affected (0.04 sec)
Records: 6 Duplicates: 0 Warnings: 0

隔离级别

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
mysql> select @@tx_isolation;
+-----------------+
| @@tx_isolation |
+-----------------+
| REPEATABLE-READ |
+-----------------+
1 row in set (0.01 sec)

mysql> select @@global.tx_isolation;
+-----------------------+
| @@global.tx_isolation |
+-----------------------+
| REPEATABLE-READ |
+-----------------------+
1 row in set (0.00 sec)

幻读是什么?

sessionA sessionB seesionC
T1 begin;
select * from t where d=5 for update;/*Q1/ result:(5,5,5)
T2 update t set d=5 where id=0;
T3 select * from t where d=5 for update;/*Q2/ result:(0,0,5),(5,5,5) insert into t values(1,1,5);
T4
T5 select * from t where d=5 for update;/*Q3/ result:(0,0,5),(1,1,5),(5,5,5)
T6 commit;
1
2
3
1.Q1 只返回 id=5 这一行;
2.在 T2 时刻,session B 把 id=0 这一行的 d 值改成了 5,因此 T3 时刻 Q2 查出来的是 id=0 和 id=5 这两行;
3.在 T4 时刻,session C 又插入一行(1,1,5),因此 T5 时刻 Q3 查出来的是 id=0、id=1 和 id=5 的这三行。

其中,Q3 读到 id=1 这一行的现象,被称为“幻读”。也就是说,幻读指的是一个事务在前后两次查询同一个范围的时候,后一次查询看到了前一次查询没有看到的行。

1
2
1.在可重复读隔离级别下,普通的查询是快照读,是不会看到别的事务插入的数据的。因此,幻读在“当前读”下才会出现。
2.上面 session B 的修改结果,被 session A 之后的 select 语句用“当前读”看到,不能称为幻读。幻读仅专指“新插入的行”。

幻读有什么问题?

  • 首先是语义上的。
  • 其次,是数据一致性的问题。
  • 也就是说,即使把所有的记录都加上锁,还是阻止不了新插入的记录

如何解决幻读?

产生幻读的原因是,行锁只能锁住行,但是新插入记录这个动作,要更新的是记录之间的“间隙”。因此,为了解决幻读问题,InnoDB 只好引入新的锁,也就是间隙锁 (Gap Lock)。间隙锁和 next-key lock 的引入,帮我们解决了幻读的问题,但同时也带来了一些“困扰”。间隙锁的引入,可能会导致同样的语句锁住更大的范围,这其实是影响了并发度的。

  • 间隙锁是在可重复读隔离级别下才会生效的