大事务会让从库卡死而非变慢,是因为sql thread必须等整个事务执行完才推进位点、更新seconds_behind_master,期间后续binlog全部排队冻结;实操需按主键分段(如id between)、每批5000–10000行、显式commit、记录断点,并禁用offset和无索引时间字段分页。

为什么大事务会让从库卡死,而不是变慢?
因为 MySQL 从库的 SQL Thread(或 MTS 下的 worker)必须等整个事务完全执行完,才能推进位点、更新 Seconds_Behind_Master。一个改 50 万行的 UPDATE,哪怕只花 8 秒,在这 8 秒里,后续所有 binlog 都排队不动,延迟值会线性上涨——不是“慢慢追”,而是“彻底冻结”。你看到的延迟跳到 300 秒,往往就源于这一条没提交的事务。
按主键分段拆分 UPDATE/DELETE 的实操要点
别用 LIMIT + OFFSET,它在并发写入下极易漏行或重复;也别只靠 ORDER BY created_at LIMIT 1000,若该字段没索引,每次排序结果不一致,语义就不可控。
- 先查边界:
SELECT MIN(id), MAX(id) FROM t WHERE status = 'pending',拿到完整处理范围 - 固定步长分片:
WHERE id BETWEEN 100000 AND 109999 AND status = 'pending',每批控制在 5000–10000 行 - 每次执行后检查
ROW_COUNT():返回 0 就退出循环,避免空跑 - 每批末尾必须显式
COMMIT,确保事务原子性与从库可并行回放 - 脚本要记录断点,比如把刚处理完的最大
id写入replica_split_checkpoint表,中断后能从id > ?继续
INSERT INTO SELECT 类大事务怎么安全拆?
这类语句最隐蔽,也最容易被当成“只读”忽略。它不锁表但生成巨量 binlog,尤其在 ROW 格式下,每行变更都记镜像,体积翻倍。
- 禁止直接
INSERT INTO dst SELECT * FROM src,必须加范围条件 - 优先按源表主键分段:
SELECT * FROM src WHERE id BETWEEN @start AND @end - 目标表要有对应索引,否则每批
INSERT可能因排序或锁升级拖慢整体节奏 - 每批后加
SLEEP(0.1)(可选),缓解从库瞬时 IO 压力 - 确认主库
binlog_format = ROW,STATEMENT模式下拆分无效,函数、临时表仍会被重放为单条语句
哪些“小事务”其实更危险?
事务大小不看 SQL 长度,而看实际修改的数据页数、锁持有时间、binlog 体积。以下看似轻量的操作,常是延迟真凶:
UPDATE t SET status=1 WHERE create_time :没索引就全表扫描+锁全表,哪怕只改 100 行,也可能锁住 10 万行-
ALTER TABLE t ADD COLUMN x INT DEFAULT 0:MySQL 5.7+ 虽支持 online DDL,但仍是长事务,阻塞 SQL Thread - ORM 批量操作如
Django bulk_create(..., batch_size=10000):若未设batch_size或设得过大,实际凑成隐式大事务 - 子查询型
UPDATE:UPDATE t1 SET a=(SELECT b FROM t2 WHERE t2.id=t1.id),若t2.id无索引,每个t1行都触发一次全表扫描
真正关键的不是“有没有拆”,而是“拆得是否可验证、可中断、可重试”。生产环境里,一次网络抖动就可能中断脚本,没断点记录的拆分,等于没拆。











