知识卡片

为保证复制一致性,InnoDB的非阻塞读会被迫加锁

普通读书笔记卡

内容

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

参考来源

- 位置:《高性能MySQL:第3版》第10章"复制"10.7.12节"InnoDB加锁读引起 的锁争用"(源文件:_epub-src/OEBPS/Text/part0017.xhtml) - 结论依据:原文明确"正常情况下,InnoDB的读操作是非阻塞的,但在某些 情况下需要加锁。特别是在使用基于语句的复制方式时,执行INSERT... SELECT操作会锁定源表上的所有行。MySQL需要加锁以确保该语句的执行 结果在主库和备库上是一致的……基于行的复制由于记录了数据的变化而 非语句,因此不会存在这个问题",直接说明加锁的触发条件、目的及 基于行复制天然不受此影响的对比。 - 原始内容:正常情况下,InnoDB的读操作是非阻塞的,但在某些情况下 需要加锁。特别是在使用基于语句的复制方式时,执行INSERT...SELECT 操作会锁定源表上的所有行。