news 2026/10/31 10:56:35

MySQL 锁机制详解|表锁、行锁、意向锁,死锁成因与解决方案

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
MySQL 锁机制详解|表锁、行锁、意向锁,死锁成因与解决方案

前言

MySQL 锁是并发场景下数据库控制资源竞争的核心机制。很多开发写 SQL 的时候,经常遇到事务卡住、更新超时、死锁报错,但是不知道根源是锁。 很多人混淆 MyISAM 和 InnoDB 锁差异,分不清行锁、表锁、意向锁,尤其对临键锁(Next-Key Lock)和死锁理解模糊。

核心前提:

  • MyISAM:只支持表级锁,不支持事务,不支持行锁;读锁共享,写锁互斥。
  • InnoDB:支持事务,支持行级锁 + 表级意向锁,是我们生产环境主流引擎。 InnoDB 锁是基于索引实现!不走索引,行锁会升级为表锁(坑点)。

一、锁粒度:表锁 vs 行锁

1.1 MyISAM 表锁(表级锁)

对整张表加锁。分为共享读锁(S 锁)、排他写锁(X 锁)

  • 读锁(S):多个会话可以同时加读锁;读的时候阻塞写
  • 写锁(X):写锁独占;写阻塞读、写阻塞写

特点:开销小,加锁快;无死锁 锁粒度大,并发差。只要一条记录写操作,整张表被锁住。

适用:只读报表、静态数据,生产业务几乎不用 MyISAM。

1.2 InnoDB 行锁(行级锁)

锁粒度是单行记录,只锁定访问的那一行。 同样分为:

共享锁 S(读锁):多个事务可以同时加 S 锁;S 和 S 兼容,S 与 X 互斥

select * from user where id = 1 lock in share mode; -- 手动加共享锁

排他锁 X(写锁):事务修改数据自动加 X 锁;X 锁和任何锁都互斥

select * from user where id = 1 for update; -- 手动加排他锁 update user set name='xxx' where id=1; -- update/delete自动加X锁

优点:粒度小,并发高,只锁目标行

缺点:加锁开销大,会产生死锁

重点坑:行锁是基于索引实现!如果 where 条件没有命中索引,InnoDB 无法定位行,会退化成表锁!

1.3 InnoDB 意向锁(Intention Lock)【高频】

意向锁属于表级锁,意向锁之间互相兼容;意向锁和真实 S/X 锁互斥。 分为:

  • 意向共享锁 IS:事务打算给表里面某行加 S 锁,先给表加 IS 锁
  • 意向排他锁 IX:事务打算给表里面某行加 X 锁,先给表加 IX 锁

意向锁作用:提升表锁判断效率如果没有意向锁,当事务想要加表锁时,需要遍历表中所有行,判断是否有行锁; 有了意向锁:只需要判断表上有没有意向锁,快速判断能不能加表锁。

意向锁兼容矩阵:

S(表共享)X(表排他)ISIX
S兼容互斥兼容互斥
X互斥互斥互斥互斥
IS兼容互斥兼容兼容
IX互斥互斥兼容兼容

意向锁不需要手动操作,InnoDB 自动维护。

二、InnoDB 行锁细分:记录锁、间隙锁、临键锁(Next-Key Lock)

InnoDB 默认隔离级别:RR(可重复读),为了解决幻读问题,使用临键锁。

RC(读已提交)隔离级别,没有间隙锁,只有记录锁,但会出现幻读。

2.1 记录锁 Record Lock

锁定索引中具体的某一行记录。主键 / 唯一索引等值查询,命中存在数据时,加记录锁。where id = 10,id 为主键且存在这条记录,锁定 id=10 这一行。

2.2 间隙锁 Gap Lock

锁定索引记录之间的间隙,不包含记录本身,防止其他事务在间隙插入数据,解决幻读。

等值查询,但是这条记录不存在,会加间隙锁。 例:id 主键,表中有 1,5,10;查询 where id=7,不存在,则锁住 (5,10) 这个间隙,其他事务无法插入 id=7。

2.3 临键锁 Next-Key Lock(RR 默认行锁算法)

临键锁 =记录锁 + 间隙锁,左开右闭区间。是 RR 隔离级别默认加锁算法。 索引:1,5,10 Next-Key 区间:(-∞,1], (1,5], (5,10], (10,+∞) 查询 id=5,临键锁区间 (1,5],锁住 5 这条记录 + 1~5 之间的间隙。

等值查询,命中唯一索引存在数据,临键锁退化成记录锁; 等值查询,记录不存在,临键锁退化成间隙锁。

幻读:同一个事务内,相同查询,前后读到行数不一样(别的事务插入新数据)。RR 依靠临键锁解决幻读。

三、死锁

3.1 死锁四大必要条件,缺一不可

  1. 互斥条件:资源排他,一个资源同一时间只能被一个事务持有(锁互斥特性)
  2. 请求与保持条件:事务已经持有锁,同时请求获取新锁,不释放已有锁
  3. 不可剥夺条件:已经获取的锁,不能被其他事务强行抢占,只能自己释放
  4. 循环等待条件:多个事务形成锁等待循环链,事务 A 等 B 的锁,B 等 C,C 等 A

四个条件同时满足,死锁产生;破坏任意一个条件,就可以解除死锁。

3.2 死锁示例

事务 1:

begin; update user set name='a' where id=1; -- 获取id=1行X锁 update user set name='b' where id=2; -- 请求id=2锁

事务 2:

begin; update user set name='b' where id=2; -- 获取id=2行X锁 update user set name='a' where id=1; -- 请求id=1锁

事务 1 持有 id1,等 id2;事务 2 持有 id2,等 id1 → 循环等待,死锁。

InnoDB 会自动检测死锁,回滚代价较小的事务,抛出Deadlock found when trying to get lock。

3.3 如何定位死锁

开启死锁日志,查看show engine innodb status;,找到LATEST DETECTED DEADLOCK,查看死锁事务、锁、SQL。

3.4 死锁规避方案(生产实践)

  1. 统一资源访问顺序:所有事务更新多行,都按固定顺序更新(先 id1 再 id2),破坏循环等待条件。最有效!
  2. 事务尽量简短,尽早提交,减少锁持有时间;
  3. 避免大事务,不要在事务中做耗时操作;
  4. 尽量使用主键 / 唯一索引更新,缩小锁范围;不走索引会锁整张表;
  5. 避免一次性锁多条记录;
  6. 业务层增加重试机制,捕获死锁异常重试。

四、锁等待超时

死锁是循环等待,InnoDB 自动检测回滚; 锁等待超时:单纯拿不到锁,没有循环等待。

参数:innodb_lock_wait_timeout默认 50 秒。超过时间直接报错。

五、RC 和 RR 隔离级别锁差异总结

隔离级别锁机制幻读
RC 读已提交只有记录锁,无间隙锁存在幻读
RR 可重复读默认 Next-Key 临键锁,包含记录 + 间隙锁解决幻读

RC 并发性能略高,但是存在幻读;MySQL 默认 RR。

六、生产环境锁优化最佳实践

  1. 更新语句一定要走索引,避免行锁升级表锁;update 条件字段必须建索引;
  2. 事务尽量小而短,不要长事务,长事务会长时间占用锁,增加锁冲突;
  3. select 普通查询不加锁(快照读);for update/lock in share mode 是当前读,会加锁;
  4. 尽量减少事务内锁的行数;
  5. 统一更新顺序,规避死锁;
  6. 监控:锁等待、死锁次数、长事务告警。

快照读 vs 当前读: 普通 select 是快照读,读取 undo 日志版本,不加行锁; update、delete、for update、lock in share mode 是当前读,读取最新数据,加行锁。

七、面试高频问答

Q1:InnoDB 行锁是锁行还是锁索引?

A:锁索引!行锁是加在索引上,如果 SQL 没有索引,无法定位行,退化成表锁。

Q2:意向锁作用?意向锁之间互斥吗

A:意向锁是表级锁,标记表内存在行锁;意向锁之间互相兼容;意向锁和表 S/X 锁互斥。

Q3:临键锁是什么,解决什么问题?

A:Next-Key 临键锁 = 记录锁 + 间隙锁,RR 隔离级别默认锁算法,用来解决幻读。

Q4:死锁四个必要条件,怎么避免死锁?

A:互斥、请求保持、不可剥夺、循环等待;破坏任意一个就不会死锁。最常用:统一资源访问顺序,破坏循环等待。

Q5:RC 和 RR 在锁上最大区别?

A:RC 没有间隙锁,只有记录锁,会出现幻读;RR 有间隙锁 + 临键锁,防止幻读。

Q6:select * from table where id=1 会加行锁吗?

A:普通 select 快照读不加锁;select ... for update /lock in share mode 当前读,才加锁。

结语

MySQL 锁机制是并发数据库核心难点,非常喜欢深挖。

重点记住:MyISAM 只有表锁;InnoDB 行锁基于索引,RR 下临键锁防幻读;意向锁是表锁用于快速判断锁冲突;死锁四大条件,统一访问顺序是解决死锁最有效的手段。

写业务 SQL 的时候,一定要思考这条 SQL 会加什么锁,锁范围多大,防止并发场景锁等待、死锁故障。

版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
网站建设 2026/10/31 10:52:52

企业微信外部群机器人实战:突破限制,主动发送群消息全流程

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

作者头像 李华
网站建设 2026/10/31 10:52:38

合同OCR提取与关键要素抽取:从纸质档案到结构化数据的实战指南

一、企业存量合同的数字化痛点凡是做过合同档案管理这件事的人, 都明白这样一个实际情况, 那就是对于一家规模处于中等水平的企业来讲, 历经十年所积攒起来的纸质合同、扫描件以及传真件, 常常会超过十万份这个数量。这些合同有的放置在档案柜里, 有的散落在各个部门的文件夹之…

作者头像 李华
网站建设 2026/10/31 10:42:29

【GATT】规范精讲[14]: 服务表的指纹——Database Hash 是怎么算出来的

前面讲过 GATT Service 里有一个叫 Database Hash 的特征,值是 16 字节,客户端拿它当服务表的指纹。一旦指纹变了,就说明服务器的服务定义改了,客户端得重新发现。但这个指纹具体怎么算?哪些属性参与、哪些不参与?字节怎么拼?规范 用一个完整的示例把这件事讲透了。 目录…

作者头像 李华
网站建设 2026/10/31 10:40:46

Qt自定义委托实现定制输入的效果

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

作者头像 李华