对幻读的思考
幻读的定义
幻读指的是一个事务在前后两次查询同一个范围的时候,后一次查询看到了前一次查询没有看到的行。
幻读发生的条件
幻读仅发生在“当前读”的情况下。
请思考一下,为什么只有在当前读下会发生?
如果是快照读,在该事务之后的其他事务的变更,当前事务一定看不到,不可能发生幻读。
问题:
表结构:
CREATE TABLE `t` (
`id` int NOT NULL,
`c` int DEFAULT NULL,
`d` int DEFAULT NULL,
PRIMARY KEY (`id`),
KEY `c` (`c`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_0900_ai_ci;
初始数据:
T3时刻情况下,session A是算发生了幻读吗?
不算,session A为修改了旧行而查询到了数据。只有“新插入的行”才算幻读。
幻读带来的问题
语义上的问题:select ... for update;
的语义是锁住所有符合条件的记录,不允许进行读写操作(即加了写锁)。而幻读会使得插入新的符合条件的记录,明显破坏了该条SQL语义。
数据一致性问题:会导致binlog写入日志的顺序问题,导致备份库/从库的数据不一致问题。
如何避免幻读
间隙锁。保证了行与行之间拒绝插入了新的记录。
间隙锁和行锁合称 next-key lock
,每个 next-key lock
是前开后闭区间。
间隙锁的问题:因为不同间隙锁之间并不互斥,可能导致不同session的相互等待导致死锁,使得并发度降低,锁住更大的范围。
那么如何取消使用间隙锁?
间隙锁只会在可重复读级别下生效,所以把隔离级别调整为读提交即可。(其实读提交也是会发生幻读,但是好像认为这算feature,在可重复读情况下这算bug???)
但是需要解决数据和日志可能不一致的问题,需要将binlog_format=row
。
拓展阅读
Binary Logging Formats :binlog
的格式有statement、row、mixed。
statement:记录执行的SQL。
row:记录每一条记录被修改就结果。
mixed:DDL使用statement、DML使用row。