大事务在生产环境是定时炸弹,因其导致主从延迟、锁表和OOM。必须拆分为小事务,按id范围分片、显式事务、主动限速,避免全表扫描与隐式类型转换,监控binlog增长与从库SQL线程状态。

不能直接跑大事务,必须拆分;否则主从延迟、锁表、OOM 都会立刻找上门。
为什么大事务在生产环境等于定时炸弹
一个 DELETE FROM orders WHERE status = 'expired' 扫描 800 万行,在主库可能 12 秒就提交了,但 binlog 会打包成单个巨量 event。从库 SQL 线程只能串行重放——这期间 Seconds_Behind_Master 直接跳到 300 秒以上,且无法被 slave_parallel_workers 加速,因为并行调度只看“事务之间”,不切“事务内部”。
更隐蔽的风险是:事务未提交前,所有已扫描但未删除的行都持有行锁 + 占用 undo log 空间,容易触发 Lock wait timeout exceeded 或 undo log full 报错。
- 大事务不会触发并行复制,只会被当做一个原子单元排队
- autocommit 开启时,每条语句自动提交,但
WHERE条件没索引会导致全表扫描+锁升级 - 用
ORDER BY created_at LIMIT 1000删除,MySQL 可能先排序再删,性能雪崩
安全拆分三要素:范围切片 + 显式事务 + 主动限速
核心不是“怎么删”,而是“怎么保证不漏、不重、不卡”。时间字段(created_at)在高并发写入下极易漏数据,必须用唯一递增字段(如 id)做分片边界。
- 每次删除后记录最大已处理
id,下次从WHERE id > ?继续,而不是依赖时间窗口 - 每批控制在
1000–5000行,单行越宽,批次越小;避免单次操作触发慢日志或锁等待超时 - 必须
SET autocommit = 0,显式START TRANSACTION+COMMIT,否则每批都会刷 binlog、加重主库压力 - 加
DO SLEEP(0.05)或SLEEP(0.1),防止磁盘 IO 被打满,也给从库喘息时间
两种实操方案选型与避坑点
方案选型取决于表结构和业务约束,不是所有场景都能套用存储过程。
- 按主键范围删(推荐):
DELETE FROM orders WHERE id BETWEEN ? AND ?—— 要求有自增/有序主键,且删除条件可映射为 ID 区间(例如“保留最近 90 天”可先查出最小id) - 游标式循环删:
DELETE FROM orders WHERE status = 'expired' ORDER BY id LIMIT 1000—— 必须带ORDER BY+ 索引字段,否则 MySQL 可能放弃索引走全表扫描 - 禁止用
ORDER BY created_at:即使created_at有索引,高并发插入下id和created_at并不同步,会导致重复删或漏删 - 执行前务必确认
binlog_row_image = FULL(默认值),否则 ROW 格式下部分列缺失可能影响从库回放
监控与兜底:别等报警才反应
靠 Seconds_Behind_Master 发现延迟已经是滞后指标。真正要盯的是主库 binlog 的“生产节奏”:如果 mysql-bin.0000xx 文件 1 分钟内涨了 150MB,基本就是大事务正在发生。
- 用
SHOW MASTER LOGS查当前 binlog 文件大小变化趋势 - 低峰期运行前,先
EXPLAIN确认删除语句是否走了索引,避免隐式类型转换导致索引失效 - 提前在从库开
SHOW PROCESSLIST观察 SQL 线程状态,若长期卡在Reading event from the relay log,说明 relay log 正在积压 - 拆分脚本里必须包含异常退出逻辑(如遇到死锁自动重试 3 次后暂停),不能让失败事务一直 hold 住连接
最易被忽略的是隐形大事务:比如 ORM 自动开启事务但忘记提交,或存储过程中未显式 COMMIT。这类事务不产生明显 DML,却长期占用 undo log 和锁资源,得靠 SELECT * FROM information_schema.INNODB_TRX 定期巡检。


















