sql线程被从库其他连接持锁阻塞,是因为其单线程回放relay log时需申请行锁、间隙锁或mdl锁,但被报表查询、长事务或ddl等未释放的锁阻塞,表现为last_sql_error为1205、state卡在updating或waiting for table metadata lock,seconds_behind_master突增而exec_master_log_pos停滞。

SQL线程被从库其他连接持锁阻塞
MySQL主从复制的SQL线程是单线程(默认)或有限并行线程,它在重放relay log时,会以事务为单位执行INSERT/UPDATE/DELETE。一旦从库上存在另一个连接(比如报表查询、监控脚本、误连的应用)正在对同一张表加锁且未释放,SQL线程就会卡住——不是它不想执行,而是它申请不到所需的行锁、间隙锁或MDL锁。
常见现象包括:SHOW SLAVE STATUS\G中Last_SQL_Error显示Error 'Lock wait timeout exceeded; try restarting transaction',Last_SQL_Errno为1205;SHOW PROCESSLIST里SQL线程State长期停在Updating或Waiting for table metadata lock,Time值持续增长。
- 最典型诱因:从库没设
read_only = ON,应用或定时任务直接往GC_Price_Archive这类业务表写入数据,和SQL线程更新冲突 - 长事务SELECT
FOR UPDATE或未提交的UPDATE持有行锁/间隙锁,SQL线程回放时无法获取对应锁 - 主库发来的大事务(如批量
INSERT INTO ... SELECT),在从库执行时间长,期间更易撞上其他连接的锁
MDL锁等待导致DDL阻塞SQL线程
当主库执行DDL(如ALTER TABLE order_info ADD COLUMN ext_info varchar(255)),该操作会生成一个metadata lock事件写入binlog,并在从库由SQL线程重放。但SQL线程执行前必须先获取目标表的MDL写锁;如果此时从库上有长查询(如SELECT COUNT(*) FROM order_info)正持有该表的MDL读锁且未结束,SQL线程就会无限期等待,直到lock_wait_timeout超时触发1205错误。
这类问题特征明显:Seconds_Behind_Master突增,CPU使用率可能飙升,而Exec_Master_Log_Pos几乎不动。
- DDL本身不慢,但等待MDL锁的过程不可见于慢日志,容易被忽略
-
SHOW PROCESSLIST里看不到“阻塞者”,只看到SQL线程在等锁;真正持锁的是那个长时间运行的SELECT或未提交事务 - 必须通过
SHOW ENGINE INNODB STATUS\G查TRANSACTIONS区块,或用SELECT * FROM sys.innodb_lock_waits交叉定位blocking_pid
大事务回放期间锁竞争加剧
大事务本身不是锁等待的根源,但它显著放大了锁等待发生的概率和影响。一个修改10万行的事务,在从库单线程回放时可能耗时数十秒甚至几分钟——这段时间内,任何试图访问相同索引范围的查询或DML,都可能与SQL线程形成锁竞争。
例如,主库执行UPDATE users SET status = 1 WHERE created_at (无索引),从库SQL线程会全表扫描并逐行加X锁;此时若有另一个连接执行<code>SELECT * FROM users WHERE id = 12345 FOR UPDATE,就可能因间隙锁或临键锁重叠而被阻塞。
- 大事务在
INFORMATION_SCHEMA.INNODB_TRX中表现为trx_rows_modified > 10000且trx_state = 'RUNNING' - 即使开启了并行复制(
slave_parallel_workers > 0),若slave_parallel_type仍为DATABASE,单库内大事务依然串行回放 - 伪大事务更隐蔽:代码中未显式BEGIN,但循环内反复执行
INSERT且autocommit = 1,每句都是独立小事务,却因高频率触发大量锁争用
为什么KILL阻塞连接能立刻恢复同步
KILL操作之所以有效,是因为InnoDB的锁是事务级的:只要阻塞方事务没提交或回滚,它持有的所有锁(行锁、间隙锁、MDL锁)就不会释放。SQL线程一直在等那个锁,直到innodb_lock_wait_timeout(默认50秒)超时,抛出1205并中止当前事务回放。
执行KILL blocking_pid后,MySQL强制终止该会话,InnoDB自动回滚其未提交事务,立即释放全部锁。SQL线程下一次重试就能立刻拿到锁,继续执行后续relay log事件。
但这只是止血,不是根治——如果每天都有报表在users表上跑COUNT(DISTINCT phone),就得把这类查询迁移到专用只读从库,或改用READ COMMITTED隔离级别降低锁粒度。











