update语句默认加行级锁,具体为:命中唯一索引加record lock,非唯一索引加next-key lock,无索引则退化为表锁;即使影响0行,只要匹配索引范围仍持锁。

UPDATE 语句默认加什么锁?
MySQL 的 UPDATE 在可重复读(RR)隔离级别下,默认走行级锁,但具体锁类型取决于 WHERE 条件是否命中索引。
- 命中唯一索引(如主键、UNIQUE 字段)→ 加
record lock(单行记录锁),不锁间隙 - 命中非唯一索引 → 加
next-key lock(记录锁 + 间隙锁),可能锁住一段范围 - 没走索引(全表扫描)→ 退化为表级锁(
table lock),并发极低,且容易被 kill
常见错误现象:UPDATE users SET status = 1 WHERE name = 'alice' 慢、阻塞其他事务,大概率是 name 没建索引。用 EXPLAIN 看执行计划,确认 type 是 ref 或 const,不是 ALL。
SELECT ... FOR UPDATE 和 UPDATE 的锁行为差异
两者都加写锁,但触发时机和锁粒度控制逻辑不同。
-
SELECT ... FOR UPDATE:在查询阶段就加锁,锁住满足 WHERE 的所有记录(含间隙),后续UPDATE只是修改已锁定的行,不重新加锁 -
UPDATE:先查再锁,优化器可能基于执行计划提前释放部分临时结果集上的锁,尤其在子查询或 JOIN 场景中 - 性能影响:显式
FOR UPDATE更可控,适合需要“查-判-改”三步逻辑;直接UPDATE更轻量,但条件复杂时锁范围难预估
典型陷阱:用 SELECT id FROM orders WHERE status = 0 LIMIT 1 查到 ID 后再 UPDATE orders SET status = 1 WHERE id = ?,中间可能被并发修改——这属于应用层漏锁,必须用 SELECT ... FOR UPDATE 或原子 UPDATE ... WHERE status = 0 LIMIT 1。
WHERE 条件带 OR 为什么容易锁全表?
MySQL 对含 OR 的 WHERE 条件优化能力弱,常导致索引失效或无法合并多个索引路径。
- 例如:
UPDATE t SET x = 1 WHERE a = 1 OR b = 2,即使a和b都有索引,也可能走index_merge失败,回退到全表扫描 - 检查方法:执行
EXPLAIN FORMAT=TRADITIONAL,看key是否为空、rows是否异常大 - 兼容性风险:MySQL 5.6+ 支持
index_merge,但 5.7 之前对OR下的锁范围判断不稳,某些版本会锁住所有扫描过的行,哪怕最终没更新
替代方案:拆成两个独立 UPDATE(注意事务内顺序和死锁风险),或用 UNION ALL + 临时表预过滤,避免在高并发写场景用 OR。
UPDATE 影响 0 行时还持有锁吗?
持有。只要 WHERE 条件匹配了索引范围,锁就加了,不管最终有没有行被修改。
- 例如:
UPDATE accounts SET balance = balance - 100 WHERE user_id = 123 AND balance >= 100,若余额不足,WHERE 不成立,但user_id = 123对应的索引记录(或间隙)仍被next-key lock锁住 - 这是防止幻读的关键机制,但也容易引发隐蔽阻塞:A 事务 UPDATE 找不到数据却卡住 B 事务 INSERT 相同范围的新记录
- 验证方式:在另一个会话执行
SELECT * FROM performance_schema.data_locks,能看到 LOCK_DATA 包含空值或最小/最大边界
真正释放锁的唯一时机是事务结束(COMMIT 或 ROLLBACK),不是语句执行完。这点在重试逻辑、超时处理、连接池复用场景里特别容易被忽略。










