update本身不锁表,但索引缺失、事务未提交或加锁顺序混乱会导致行锁/间隙锁失控,等效锁表现象;须确保where走索引、事务短小、多表操作按固定顺序加锁。

UPDATE 操作本身不会“锁死”整张表,但会因索引缺失、事务未提交或加锁顺序混乱,导致其他操作被阻塞——现象像锁表,本质是行锁/间隙锁失控。
WHERE 字段没索引或索引失效
这是最常见原因。InnoDB 的 UPDATE 默认走行锁,但前提是 WHERE 条件能命中索引。一旦 mark_id 没建索引,或写了 WHERE DATE(created_at) = '2026-05-01' 这类函数操作,优化器就会退化为全表扫描,给所有扫描行加记录锁,再顺带激活间隙锁,等效锁表。
- 用
EXPLAIN看执行计划:type是ALL、rows接近表总行数,基本可断定没走索引 - 检查索引是否存在:
SHOW INDEX FROM users,注意前缀索引、组合索引最左匹配原则 - 避免隐式转换:比如
mark_id是VARCHAR,却传入数字123,索引直接失效 - 有索引不等于被用:高重复值字段(如只有两个状态的
status)可能让优化器放弃索引,得看EXPLAIN FORMAT=JSON中的used_key
事务没提交,锁一直挂着
锁的生命周期不是 SQL 执行时间,而是从 BEGIN 到 COMMIT 的整个事务窗口。哪怕 UPDATE 本身毫秒完成,只要事务没结束,锁就持续存在;期间若发生范围扫描,InnoDB 还会自动补上间隙锁,把相邻空隙也锁死。
- ORM 框架(如 Spring 的
@Transactional)容易在事务里混入 HTTP 调用、日志写入等耗时操作,把锁拖长 - 连接池复用时,上一个请求忘了
COMMIT,下一个请求复用同一连接,锁直接继承 - 查长事务:
SELECT * FROM information_schema.INNODB_TRX,重点关注trx_started和trx_state
多表 UPDATE 或 SELECT FOR UPDATE 加锁顺序不一致
死锁不是并发高导致的,而是多个事务以不同顺序锁定同一组行时立刻触发的。比如事务 A 先锁 order 再锁 user,事务 B 反过来,InnoDB 加锁就成环。
- 所有多表操作必须统一加锁顺序:按表名字母序(
order_item→order→user),或按业务主次(核心订单流必须是order→order_item→payment) - 动态拼 SQL 时禁用条件分支控制表序,例如不能写
if (type == 'refund') { update user; update order; } -
SELECT FOR UPDATE即使查不到记录,也会加间隙锁;若多个并发查同一个不存在的order_no,全卡在同一个间隙上,极易死锁 - 验证是否真走索引:
EXPLAIN FORMAT=TRADITIONAL看key和Extra(不含Using filesort)
分批 UPDATE 仍锁表?你可能没用对游标
UPDATE ... LIMIT N 不解决锁问题——它只限制修改行数,WHERE 仍可能扫全表。真正安全的分批,必须基于有索引的单调字段(如 id、created_at)做游标式推进。
- 别用
LIMIT 1000 OFFSET 1000:并发执行时数据变动会导致偏移错乱 - 每批用
WHERE id > ? AND id ,而非 <code>LIMIT,确保可重复执行 - 每批单独事务,执行完立刻
COMMIT,锁立即释放 - 批大小建议 1000–5000 行:太小网络开销高,太大单次锁时间仍长
- 线上执行前必做三件事:用
EXPLAIN确认每批type至少是range;在从库跑完整流程验证结果;加简单限流(如应用层SLEEP(0.1))
真正卡住系统的往往不是慢查询,而是那个在事务里默默等了 8 秒的 Redis 响应,或者没走索引的 SELECT FOR UPDATE——它锁的不是一行,是一整个索引区间。











