mysql大事务update易oom主因是事务临时内存(undo log、sp_head::main_mem_root等)暴增,与innodb_buffer_pool_size无关;须用主键范围分块(如between)、每批commit、监控innodb_buffer_pool_wait_free及rss增长,严控sort_buffer_size、performance_schema等隐性内存源。

Update大事务为什么一跑就OOM
不是SQL写错了,是MySQL在执行单条大UPDATE时,会把所有匹配行的旧值+新值+undo log全塞进内存(尤其是sp_head::main_mem_root和InnoDB事务系统),并发一高或数据量超10万行,RSS直接飙到几个GB。这时候innodb_buffer_pool_size调再小也没用——它管不了事务临时内存。
怎么安全分块:WHERE条件必须带索引主键/唯一键
不能靠LIMIT硬切,否则可能漏更新或重复更新。正确做法是用主键范围滚动推进:
- 先查出最小和最大主键:
SELECT MIN(id), MAX(id) FROM t_user; - 按步长(比如5000)分段:
UPDATE t_user SET status=1 WHERE id BETWEEN 10001 AND 15000 AND status=0; - 每次执行后检查
ROW_COUNT()是否为0,为0说明本段无更新,可跳过 - 别用
OFFSET分页式写法(LIMIT 10000,5000),它会扫全表偏移,越往后越慢还吃内存
自动分块脚本的关键控制点
手动切太累,但写脚本要注意三件事:
- 每批
UPDATE后必须COMMIT,否则事务日志和锁持续累积;不要等整个脚本结束才提交 - 加
SLEEP(0.1)(毫秒级)防CPU打满、避免触发max_connections耗尽 - 监控
SHOW STATUS LIKE 'Innodb_buffer_pool_wait_free';,如果>0,说明缓冲池已吃紧,要立刻降速或暂停 - 别在从库上直接跑——大事务会阻塞复制,优先在主库执行并确认
Seconds_Behind_Master归零后再切下一批
容易被忽略的隐性内存放大器
就算你分了块,以下配置仍可能让每批更新悄悄吃掉额外几百MB:
-
sort_buffer_size和join_buffer_size设得太高(如设成8M),哪怕没ORDER BY或JOIN,MySQL也可能为优化器预分配 -
tmp_table_size和max_heap_table_size过大,导致UPDATE ... JOIN或子查询临时表直接进内存而非磁盘 - 开了
performance_schema且启用了memory/sql/sp_head采集,每个分块都新建一个sp_head实例,不释放 - 触发器存在时,每个分块更新都会触发一次,而触发器代码全堆在
main_mem_root里,不随事务提交释放
分块本身不解决根本问题,它只是把“一次爆内存”变成“多次可控内存”,真正要盯的是每批执行时ps aux --sort=-%mem里mysqld进程的RSS增长曲线——如果每批涨200MB且不回落,说明有内存没被回收,得回头查触发器或PS配置。











