SQL 数据修改操作锁机制解析

1次阅读

update语句默认加行级锁,但where条件必须命中索引,否则会全表扫描并逐行加锁,等效于表级锁。

SQL 数据修改操作锁机制解析

UPDATE 语句默认加什么锁?

mysql InnoDB 下,UPDATE 默认走行级锁,但前提是 WHERE 条件命中索引;否则会升级为表级锁(实际是全表扫描 + 每行加锁,效果等同于锁全表)。

常见错误现象:UPDATE users SET status = 1 WHERE name = 'alice' 执行极慢、阻塞其他事务,查发现 name 字段没索引。

  • 必须确认 WHERE 中所有字段都有对应索引,复合条件优先用联合索引
  • EXPLAIN 看执行计划,确保 typerefconst,不是 ALL
  • UPDATE 带 LIMIT 时,InnoDB 仍会对扫描到的所有行加锁(哪怕只改一行),这点容易被忽略

selectfor UPDATE 和 UPDATE 锁行为差异

SELECT ... FOR UPDATEUPDATE 都加临键锁(Next-Key Lock),但触发时机和范围不同:前者在读阶段就锁住记录+间隙,后者在写入前才加锁,且仅锁最终命中的行(含间隙)。

使用场景:需要先查再判再改的业务逻辑(如库存扣减),若用普通 SELECT + UPDATE,中间可能被并发修改;但直接上 SELECT ... FOR UPDATE 又可能锁得过宽。

  • 如果 WHERE 条件能唯一确定一行(主键或唯一索引),SELECT ... FOR UPDATEUPDATE 锁范围基本一致
  • 若条件不唯一(如 WHERE status = 0),SELECT ... FOR UPDATE 会锁住所有匹配行及其间隙,而 UPDATE 只锁实际更新的行(但扫描过程仍可能触发间隙锁)
  • 注意隔离级别影响:READ COMMITTED 下 SELECT ... FOR UPDATE 不加间隙锁,但 UPDATE 仍加(除非关闭 innodb_locks_unsafe_for_binlog,不推荐)

死锁是怎么被 UPDATE 撞出来的?

死锁不是锁多了,而是多个事务以不同顺序访问相同资源。最典型的是两个事务分别按不同索引顺序 UPDATE 同一批行。

常见错误现象:应用报错 Deadlock found when trying to get lock; try restarting transaction,但单条语句单独执行完全正常。

  • 检查事务中所有 SQL 的 WHERE 条件是否都走同一索引,避免一个走 idx_a、另一个走 idx_b
  • UPDATE 多行时,务必按主键升序(或固定顺序)组织数据,例如先 ORDER BY id 再批量更新
  • 不要在一个事务里混用 SELECT … FOR UPDATE 和 UPDATE —— 它们锁粒度和释放时机不同,极易形成环路

UPDATE 影响行数为 0 还会加锁吗?

会。只要 WHERE 条件走索引,InnoDB 就会对匹配的索引记录(包括“不存在但符合范围”的间隙)加锁,哪怕最终没更新任何行。

性能影响:这种“空锁”常被忽视,却会导致其他事务在该间隙插入失败,尤其在高并发 INSERT 场景下引发明显阻塞。

  • SELECT ... LOCK IN SHARE MODESELECT ... FOR UPDATE 配合 ROW_COUNT() 判断是否存在,比盲目 UPDATE 更可控
  • 如果业务允许,把“不存在则插入”逻辑改用 INSERT ... ON DUPLICATE KEY UPDATE,它对唯一冲突路径的加锁更轻量
  • 监控 Innodb_row_lock_time_avgInnodb_deadlocks,长期偏高就要查空 WHERE 是否误锁间隙

锁的边界从来不在语句表面,而在索引结构、执行路径和事务包裹方式里。调参不如理清这三者的咬合关系。

text=ZqhQzanResources