mysql锁是如何实现的_mysql底层机制解析

1次阅读

行锁加在索引上,而非数据行;即使无显式索引,innodb 也会通过隐藏聚簇索引加锁。主键更新只锁主键索引,二级索引更新需锁二级索引及主键索引,全表扫描则对每条匹配记录的主键索引项加锁。

mysql锁是如何实现的_mysql底层机制解析

行锁到底加在哪儿?不是数据行,而是索引上

mysqlInnoDB 行锁从不直接锁“表里的某一行”,它锁的是**索引记录**——哪怕你没建任何索引,InnoDB 也会生成一个隐藏的聚簇索引(GEN_CLUST_INDEX),所有行锁都落在这个索引的叶子节点上。

这意味着:

  • 用主键更新:UPdate t SET v=1 WHERE id=100 → 只在主键索引上加 X
  • 用二级索引更新:UPDATE t SET v=1 WHERE name='Alice' → 先在 name 索引上加 X 锁,再回表到主键索引加 X 锁(两次加锁)
  • 没走索引的查询(如 WHERE status=1status 无索引)→ 会退化为全表扫描,对**每条匹配记录的主键索引项**逐个加锁,等效于“伪表锁”,极易引发锁争用

select ... for UPDATESELECT ... LOCK IN SHARE MODE 的真实行为

这两条语句是显式加锁的入口,但它们的效果高度依赖隔离级别和查询条件是否命中索引:

  • SELECT * FROM t WHERE id=5 FOR UPDATE(主键精确查找)→ 加 X 记录锁(LOCK_REC_NOT_GAP),只锁住 id=5 这一条
  • SELECT * FROM t WHERE age > 25 FOR UPDATE(范围查询,RR 隔离级)→ 加 Next-Key Lock(记录 + 间隙),既锁住所有满足条件的记录,也锁住这些记录之间的“间隙”,防止幻读
  • SELECT * FROM t WHERE name='Bob' LOCK IN SHARE MODE(二级索引+唯一值)→ 在 name 索引和对应主键上各加 S 锁;若 name 不唯一,则可能锁住多个主键索引项

注意:RC(读已提交)隔离级下,Next-Key Lock 会被降级为仅记录锁,间隙部分不锁 → 幻读可能发生,但锁范围更小、并发更高。

为什么 UPDATE 有时会锁全表?真相是没走索引或隐式类型转换

常见现象:一条看似简单的 UPDATE 执行极慢,且阻塞其他事务。背后往往不是“引擎故意锁表”,而是优化器被迫放弃索引:

  • 字段类型不一致:WHERE phone='13800138000',但 phoneBIGINT → 触发隐式转换,索引失效,全表扫描+逐行加 X
  • 函数操作:WHERE DATE(create_time) = '2026-01-28' → 索引无法使用,同样导致全表加锁
  • 字符集/排序规则不匹配:关联字段或 WHERE 条件中 collation 不一致,索引失效

验证方法:执行 EXPLAINtype 是否为 ALLindex,再结合 SHOW ENGINE INNODB STATUSG 查看 TRANSACTIONS 部分的锁等待详情。

死锁不是 bug,是并发路径的必然产物——如何快速定位和规避

死锁在 InnoDB 中由检测线程每秒唤醒一次主动发现,并牺牲其中一个事务(报错 Deadlock found when trying to get lock)。关键不在“避免死锁”,而在“让死锁更快暴露、更少发生”:

  • 统一 DML 顺序:多个事务更新多张表时,始终按相同顺序(如先 ordersorder_items)加锁,打破循环等待
  • 缩短事务长度:把非数据库操作(如 http 调用、日志写入)移出事务块,减少持锁时间
  • 避免在事务中用户交互:比如先 SELECT FOR UPDATE 锁住记录,等用户点击“确认”才 UPDATE —— 这会让锁持有几十秒甚至几分钟
  • 监控高频死锁点:SHOW GLOBAL STATUS LIKE 'Innodb_deadlocks' 持续增长,配合慢日志和 information_schema.INNODB_TRX 定位长事务

真正容易被忽略的一点:INSERT ... ON DUPLICATE KEY UPDATE 在有唯一索引冲突时,会先加 S 锁再升级为 X 锁,若两个事务交叉执行,极易触发死锁——这种“语法糖”背后的锁行为,比直觉复杂得多。

text=ZqhQzanResources