主从切换后事务执行失败的首要原因是新主库read_only/super_read_only未关闭;其次为旧主库长事务持有MDL锁未释放、客户端仍连旧主库、GTID不一致导致回滚失败。

因为从库没设 read_only = ON,或者有长事务/慢查询在从库上直接执行,和 SQL Thread 更新同一张表形成锁竞争——这不是复制机制故障,而是人为混用导致的资源争抢。
为什么read_only没开就容易锁住SQL线程
从库默认不是只读的,应用、定时任务、DBA手动操作都可能往表里写数据。一旦和 SQL Thread 同时操作 GC_Price_Archive 这类高频更新表,就会触发行锁或间隙锁冲突。
-
read_only = OFF时,任何连接都能执行INSERT/UPDATE/DELETE,SQL Thread 回放主库 binlog 的时候申请不到锁,等满innodb_lock_wait_timeout(默认 50 秒)就报错 1205 -
super_read_only = ON比read_only更严格,连 super 用户也不能绕过,必须一起启用才真正防住误写 - 开了
read_only后,SQL Thread 的事务不会被其他连接阻塞,但要注意:已有未提交事务仍持有锁,得先KILL掉再开
怎么快速定位正在阻塞 SQL Thread 的那个连接
别只看 SHOW PROCESSLIST 里运行时间最长的——它大概率是被卡住的,不是卡别人的。真正要抓的是“持锁不放”的那个。
- 查
SHOW ENGINE INNODB STATUS\G,翻到TRANSACTIONS区块,找TRX_WAITING的事务,记下它的trx_mysql_thread_id - 再查
SELECT * FROM INFORMATION_SCHEMA.PROCESSLIST WHERE ID = ?,确认这个线程在跑什么 SQL(比如SELECT ... FOR UPDATE或没提交的UPDATE) - 更直接的方式:
SELECT * FROM sys.innodb_lock_waits;,字段blocking_pid就是阻塞方的连接 ID - 如果
sys.innodb_lock_waits为空但延迟还在涨,说明锁已释放、只是之前超时退出了,此时重点看错误日志时间戳 +SHOW SLAVE STATUS\G中的Last_SQL_Error
SELECT ... FOR UPDATE 在从库上为啥特别危险
很多人以为只有 DML 才加锁,其实 SELECT ... FOR UPDATE 在 RR 隔离级别下会加临键锁(Next-Key Lock),既锁行又锁间隙,极易把 SQL Thread 挡在外面。
- 没走索引的
FOR UPDATE会升级为全表扫描+行锁,实际效果接近锁表 - 范围查询如
WHERE id > 100,即使有索引,也会锁住所有匹配行 + 后续插入间隙,SQL Thread 插入新记录时直接被堵死 - 监控里看到
Slave_SQL_Running_State卡在Waiting for dependent transaction to commit,基本就是这类查询在作祟 - 验证方法:
SHOW ENGINE INNODB STATUS\G中搜lock_mode X locks gap before rec,就能确认是否锁了间隙
大事务回放期间为什么更容易被锁阻塞
一个执行 10 分钟的 DELETE FROM logs WHERE ts ,本身就在从库占着锁,这时候哪怕只来一条 <code>SELECT ... FOR UPDATE,SQL Thread 就得等它结束才能继续——而它很可能根本没提交。
- 大事务期间,
Exec_Master_Log_Pos几乎不动,Seconds_Behind_Master却稳定上涨,这是典型信号 - 并行复制对单个大事务无效:binlog_format=ROW 下,整个事务就是一个 GTID 单位,worker 线程无法拆解
- 主库上查
INFORMATION_SCHEMA.INNODB_TRX,trx_rows_modified > 10000且trx_started很早,基本就是它 - 拆分不能只加
LIMIT:必须SET autocommit = 0+START TRANSACTION+COMMIT显式控制批次,否则每句都生成独立事务,binlog 膨胀更快
真正难处理的不是锁本身,而是那些没人认领的慢查询或定时任务——它们常年跑在从库上,一卡就是几小时,还经常在半夜触发。上线前没做权限隔离、没配 super_read_only、没关掉应用直连从库的配置,这些问题堆在一起,锁阻塞就成了常态。


















