UPDATE不走索引会实际锁全表;分批更新须用主键范围游标并及时提交;查锁源优先于优化SQL;上线前需在从库验证总数与逻辑。

UPDATE 不会主动锁全表,但只要 WHERE 条件没走索引、或单次更新行数过多、或事务卡住不提交,InnoDB 就会扫描大量行并逐行加锁,效果等同于锁表。
WHERE 条件没走索引 → 实际锁全表
这是最常见也最隐蔽的“伪锁表”。InnoDB 的行锁依赖索引,一旦 WHERE 字段无索引、或有索引但因隐式转换/函数操作/区分度低而失效,优化器就会退化为全表扫描,对每条扫描行加 next-key lock——扫 100 万行,就锁 100 万行。
- 用
EXPLAIN FORMAT=TRADITIONAL看type字段:值为ALL或index,基本可判定全表/全索引扫描 -
key为空,或显示的不是你建的索引名,说明没命中 - 隐式类型转换很致命:
user_id是BIGINT,却传字符串'123';phone是VARCHAR,却写WHERE phone = 13800138000 - 避免函数操作:
WHERE DATE(created_at) = '2026-10-01'会让索引失效;应改写为created_at >= '2026-10-01' AND created_at
分批 UPDATE 必须用主键范围游标,不能靠 LIMIT OFFSET
LIMIT 只限制最终修改行数,不减少扫描量。如果 WHERE 没索引,UPDATE ... LIMIT 1000 仍会全表扫描,只是最后只改前 1000 行——锁已加满,其他事务照等。
- 正确做法是用单调递增字段(如
id)做范围切片:UPDATE orders SET status = 'shipped' WHERE id > 100000 AND id - 下一批起点必须是上一批的
MAX(id),不能靠SELECT COUNT算总页数 - 禁用
OFFSET:并发时数据变动会导致偏移漂移,漏更或重复 - 每批必须单独事务,执行完立刻
COMMIT,锁才真正释放
SQL Server 用 UPDATE TOP(n) 分批,但必须配事务和 ORDER BY
SQL Server 的 TOP 语法看似简单,但缺了关键控制就会出问题:不加 ORDER BY 会导致同一批重复更新或漏更;不包在显式事务里,每条 TOP 都是独立事务,开销反而更大。
- 必须写成:
BEGIN TRANSACTION; UPDATE TOP (1000) t SET flag = 1 FROM logs t WHERE t.flag = 0 ORDER BY id; COMMIT; - 终止条件用
IF @@ROWCOUNT = 0 BREAK,别预估总行数 - 批次大小建议 1000–5000:小于 1000 提交太频繁;大于 5000 可能触发锁升级
- 禁用
WHERE id IN (SELECT ...):子查询结果可能缓存在 tempdb,几十万 ID 就吃光内存,还扩大锁范围
查锁源比优化 SQL 更优先
UPDATE 卡住时,90% 的问题不在那条语句本身,而在谁在前面占着锁不放。盲目调优 WHERE 条件或加索引,可能完全跑偏。
- MySQL 查长事务:
SELECT * FROM information_schema.INNODB_TRX ORDER BY TRX_STARTED DESC,重点关注trx_state = 'LOCK WAIT'和trx_started时间过长的记录 - SQL Server 查阻塞源头:
SELECT blocking_session_id, wait_type, wait_resource FROM sys.dm_exec_requests WHERE blocking_session_id > 0 - 所有分批脚本上线前,必须在从库或备份库完整跑一遍,确认最终更新总数和业务逻辑对得上——因为
WHERE status = 'pending'这类条件,在执行过程中可能有新插入数据混入










