UPDATE + LIMIT 不能防大事务,因LIMIT只限制修改行数,若WHERE无索引仍会全表扫描加锁;安全分批需ORDER BY+索引、记录last_id、应用层循环、预估范围、限流重试及无索引时建索引或两步走。

UPDATE + LIMIT 为什么不能直接防大事务
很多人以为写个 UPDATE ... WHERE condition LIMIT 1000 就算分批了,其实不是。MySQL 的 LIMIT 只控制“最终修改的行数”,但执行器仍可能全表扫描、全表加锁——只要 WHERE 条件没走索引,LIMIT 就是假分批。你看到的“只改了1000行”,背后可能是锁住50万行间隙、日志暴涨、主从延迟飙升。
必须配合 ORDER BY 和索引才生效
安全分批的前提是:每次都能精准定位下一批数据起点,且不漏不重。这要求:
-
ORDER BY字段必须有索引(最好是主键或唯一索引),否则LIMIT会触发文件排序+全表扫描 - 不能用
OFFSET,它随数据变动偏移,且越往后越慢(LIMIT 2000000, 1000要先跳过200万行) - 每次执行后,要记录上一批最后的排序字段值(如
last_id),下一批用WHERE id > last_id AND ... ORDER BY id LIMIT 1000
客户端循环比存储过程更可控
MySQL 存储过程里用 REPEAT ... UNTIL ROW_COUNT() = 0 看似简洁,但存在隐式事务边界风险,且无法灵活控速、监控失败批次。推荐在应用层(Python/Java/Shell)做循环:
面向 CRM 系统的 MySQL 8 数据库模式设计。当用户需要为客户关系管理(CRM)系统设计、评审、优化或生成数据库模式时,应使用此项技能。
- 每次执行前
BEGIN,更新后立刻COMMIT,绝不依赖AUTOCOMMIT=1 - 用
SELECT MIN(id), MAX(id) FROM t WHERE status = 0预估范围,避免无限循环 - 每批后加
SLEEP(0.1)(单位秒),缓解 IOPS 和复制压力 - 捕获
Lock wait timeout exceeded或Deadlock found后退避重试,别硬刚
WHERE 条件没索引时的应急解法
这是线上最常被忽略的致命点:哪怕你写了 LIMIT 100,EXPLAIN 显示 type: ALL,就等于锁表。可行方案只有两个:
- 临时建索引:
CREATE INDEX idx_status ON t(status)(DDL 期间表可读不可写,但远好过锁表几小时) - 两步走:先
SELECT id FROM t WHERE status = 0 ORDER BY id LIMIT 500拿 ID 列表,再UPDATE t SET ... WHERE id IN (1,2,3,...)(注意IN列表别超 1000 项)
别信 FORCE INDEX——没对应索引时,它只是语法通过,执行计划仍是全扫。










