知识卡片
为保证复制一致性,InnoDB的非阻塞读会被迫加锁
内容
InnoDB正常情况下的普通读是非阻塞的(依赖MVCC读快照,不需要和写操作
互斥),但在使用基于语句的复制时,有一类读会被迫破例加锁:像
INSERT ... SELECT这样”用查询结果去驱动写入”的语句,MySQL必须对
SELECT部分读到的源表行加共享锁。原因是基于语句的复制只在备库上重放
这条SQL语句本身,要保证备库重放这条语句时能读到”和主库执行时完全一样”
的源数据,就必须保证在主库上,这条语句读取源表和它写入目标表之间,没
有其他并发事务能在中途修改源表、打乱这条语句本身的执行结果——具体
机制是靠共享锁把可能冲突的并发写事务阻塞住,让这条语句的读和写实质上
被强制串行化。这带来一个容易被忽视的连带效应:一个原本不需要写锁参与
的普通查询,只因为处在基于语句复制的场景下,也会造成锁竞争、阻塞甚至
锁等待超时,而且这个成本是复制机制本身的正确性要求带来的,不是业务
逻辑设计不当造成的。缓解思路是让触发这类加锁读的事务尽量短(拆分大
INSERT...SELECT成小批次、尽快提交释放锁),或者换用SELECT INTO
OUTFILE加LOAD DATA INFILE绕开这类加锁的查询路径。也可以用参数
innodb_locks_unsafe_for_binlog直接关掉这层锁保护,但这样会让主备之间
在某些并发场景下产生数据不一致(比如两个事务的执行顺序在主备上记录
不同),书中明确不建议在多数场景下这样做。这个案例和
[[InnoDB用间隙锁而非快照检测来防止幻读]]是同一类现象的两个不同触发
原因:InnoDB平时依赖非阻塞的MVCC读,但只要有某个外部约束要求”读到的
结果必须和某个特定时刻的真实状态严格对应、不能有中途篡改”,就不得不
临时切换成加锁的方式,用牺牲并发换取这份确定性——只是这里的约束来自
复制正确性,而不是隔离级别本身的幻读防范。