MySQL从库SQL线程因锁等待停止,主因是从库未设read_only或存在长查询/未提交事务持锁阻塞,需通过INNODB_STATUS、sys.innodb_lock_waits定位阻塞源并KILL释放锁,根本解法是启用super_read_only、优化索引与隔离级别。

从库查询会抢锁,SQL线程只能干等
从库上运行的 SELECT 查询(尤其是没走索引的全表扫描、COUNT(*)、JOIN)可能持有表级锁或长时间占用行锁/MDL 锁,而复制 SQL 线程恰好要执行同一张表的 UPDATE、DELETE 或 ALTER TABLE。这时 SQL 线程状态会卡在 Waiting for table metadata lock 或 Updating,根本不是它慢,是它被业务查询“堵”住了。
典型现象:Seconds_Behind_Master 持续上涨,但 SHOW PROCESSLIST 里能看到一个长耗时 SELECT 正在跑,同时 SQL 线程状态停滞不动;SHOW ENGINE INNODB STATUS 中出现大量 lock_wait 记录,且等待对象集中在同一张表。
- MDL 锁冲突最常见:比如报表脚本执行
SELECT * FROM huge_table WHERE ...期间,SQL 线程试图回放主库发来的ALTER TABLE,必须等该SELECT结束才能获取 MDL - InnoDB 行锁放大:当从库回放
ROW格式 binlog 的UPDATE时,若目标表无主键,MySQL 会隐式全表扫描比对——此时若另一个查询正对该表做写操作,就会反复触发行锁等待 - 大结果集查询消耗大量 buffer pool 和 IO,间接拖慢 SQL 线程读 relay log 和解析事件的速度
为什么 read_only=ON 不能完全防住阻塞
read_only=ON 只阻止显式写入(INSERT/UPDATE/DELETE),但对 SELECT 完全无效。更关键的是,某些 SELECT 语句本身就会申请 MDL 锁(如带 FOR UPDATE、或查询视图/存储过程依赖的底层表),照样能和 SQL 线程抢锁。
尤其要注意这些容易被忽略的“伪只读”操作:
-
SELECT ... INTO OUTFILE—— 虽不改数据,但会持有 MDL -
SELECT查询包含函数如UUID()、NOW()(在STATEMENT复制模式下)可能触发隐式锁或执行计划异常 - 使用
LOCK TABLES ... READ(哪怕只是临时加锁)会直接阻塞所有 DDL 回放 - 备份工具如
mysqldump默认加FLUSH TABLES WITH READ LOCK,若未配合--single-transaction,会在从库长期持锁
怎么快速定位是哪个查询在挡路
别只看 SHOW PROCESSLIST 里运行时间最长的那条,重点查正在持有锁、或刚启动但已锁表的会话:
- 查 pending MDL 锁:
SELECT * FROM performance_schema.metadata_locks WHERE LOCK_STATUS = 'PENDING' AND OBJECT_SCHEMA NOT IN ('mysql', 'performance_schema', 'information_schema');,看OWNER_THREAD_ID对应哪个会话 - 查活跃事务锁争用:
SELECT trx_id, trx_state, trx_started, trx_rows_locked, trx_query FROM information_schema.INNODB_TRX WHERE trx_state = 'RUNNING' ORDER BY trx_started;,重点关注trx_rows_locked异常高或trx_query是SELECT的记录 - 查锁等待链:
SELECT * FROM performance_schema.data_lock_waits;(MySQL 8.0+),直接看到谁在等谁
注意:performance_schema 必须提前开启(performance_schema=ON),且相关 instrument(如 wait/lock/metadata/sql/mdl)需启用,否则查不到锁细节。
从库查询和复制共存的真实代价
很多人以为“从库只读就安全”,其实只要业务查询没优化好,它和复制就是资源竞争关系:共享 buffer pool、共享磁盘 IO、共享 CPU 时间片。尤其当 innodb_buffer_pool_size 没按从库实际负载调大,或者没关掉旧版残留的 query_cache,缓存抖动会让 SQL 线程频繁换页、重解析,延迟肉眼可见。
真正要解决,不能只靠 kill 查询——得把查询治理和复制配置对齐:
- 报表类查询必须加
/*+ MAX_EXECUTION_TIME(30000) */或设max_execution_time限流 - 从库
innodb_flush_log_at_trx_commit建议设为2(接受秒级日志丢失风险),避免每事务刷盘拖慢回放 - 确认
slave_parallel_workers > 0且slave_parallel_type = 'WRITESET'(MySQL 8.0+),否则单线程瓶颈永远存在 - 监控项不能只盯
Seconds_Behind_Master,要同步看Relay_Log_Space(暴涨说明 relay log 写满但 SQL 没消费)和Slave_SQL_Running_State(卡在Reading event from the relay log还是Executing)
最易被忽略的一点:从库上的慢查询日志默认可能没开,或者日志路径磁盘空间不足,导致你根本看不到是谁在偷偷拖垮复制——查之前先确认 slow_query_log=ON 且 long_query_time 设得合理(建议 ≤ 1 秒)。


















