千万级UPDATE锁表因低区分度WHERE导致全表扫描并逐行加锁;应按主键分批更新,每次用id BETWEEN加原始条件、小事务提交,并校验ROW_COUNT()。

UPDATE千万级数据时为什么一跑就锁表
因为MySQL默认用行锁,但WHERE is_sync = 0这种低区分度条件会让优化器放弃走索引,直接全表扫描——InnoDB就得从聚簇索引第一条开始逐行加锁、判断、更新。事务不提交,锁就一直挂着,其他查询/更新全被堵住。你看到的“卡死”“超时”“连接池耗尽”,根源不是SQL写错了,而是它在默默锁住几百万行。
用主键范围分批UPDATE最稳
别依赖WHERE字段本身做分页,改用主键(通常是id)切片,确保每次只扫固定范围、走索引、锁得少。操作步骤如下:
- 先查出待更新数据的最小和最大
id:SELECT MIN(id), MAX(id) FROM t_order WHERE is_sync = 0; - 按步长(比如每批5000行)循环执行:
UPDATE t_order SET is_sync = 1 WHERE id BETWEEN ? AND ? AND is_sync = 0; - 每次执行后用
SELECT ROW_COUNT();确认实际更新行数,避免漏掉或重复 - 每批后加
COMMIT(显式事务),别靠自动提交——否则单个事务太大仍会撑爆undo log
WHERE条件必须保留,防止跨批次污染
光靠id BETWEEN不够,必须叠加原始业务条件,比如AND is_sync = 0。否则:如果某行在上一批已更新为1,下一批因没加这个条件,可能被重复SET为1(虽无副作用),更危险的是——若中间有新插入is_sync = 0的行,其id落在当前批次范围内,就会被误更新。加上该条件,MySQL会在索引查找时直接过滤掉已变更的行,语义安全。
别碰临时禁用索引或nologging这类高危操作
有人想“先删is_sync索引→批量UPDATE→再建索引”,这在MySQL里极不推荐:二级索引删除重建期间,所有DML都会受阻;且is_sync字段本身更新频繁,删它索引反而让其他查询变慢。Oracle可用nologging,但MySQL没有等效机制,SET sql_log_bin = 0仅适用于主从架构且风险极高,生产环境严禁使用。真正省事又安全的做法,就是老老实实用主键分批+带条件+小事务。
ROW_COUNT()是否为预期值——比如某批返回0,说明该范围已无目标数据,应立即退出循环,而不是硬着头皮继续跑完所有批次。

















