题目

请介绍 MySQL 中有哪些锁类型,以及它们各自的作用和使用场景。

参考答案

开篇先打个预防针

MySQL 的锁机制,说简单也简单,说复杂嘛——复杂到能单独写一本书。面试的时候如果你一上来就背"全局锁、表级锁、行级锁......",面试官大概率会微微一笑,然后追问一句:"那行级锁又有哪几种?"

所以咱换个思路——从大到小,逐层剥洋葱。先看大局,再钻细节,保证你自己讲得清、对方听得懂。


一、按粒度分层——三大"祖宗锁"

MySQL 的锁如果按照锁的范围来分,从上到下有三个层级。就像公司的管理架构——管全公司的、管部门的、管个人的。

1. 全局锁——"整库冻结"
FLUSH TABLES WITH READ LOCK;   -- 全局读锁,整个数据库只读
UNLOCK TABLES;                 -- 释放

使用场景:全库逻辑备份。现在有了 mysqldump --single-transaction,基本不用这招了。面试里提一嘴"我知道有这么个东西,但 MVCC 时代有了更好的替代方案"就够了。

2. 表级锁——"这张表我包了"
LOCK TABLES user READ;   -- 表级读锁(共享锁)
LOCK TABLES user WRITE;  -- 表级写锁(排他锁)
UNLOCK TABLES;         

表锁的优点是开销小、不会死锁(一次性拿锁,没有逐行争抢)。缺点是并发低——一个人锁了整张表,其他人全得排队。

除了显式的 LOCK TABLES,还有两类隐式的表级锁很常见:

元数据锁(MDL,Metadata Lock):MySQL 5.5 引入,不需要你手动加。CRUD 时自动加 MDL 读锁,改表结构(ALTER TABLE)时自动加 MDL 写锁。如果你在线上执行了一个大表 DDL 被阻塞,八成就是有人在跑慢查询占着 MDL 读锁不撒手。

自增锁(AUTO-INC Lock):跟自增主键(AUTO_INCREMENT)配合使用。插入数据时自动加,保证自增值不会重复。MySQL 8.0 后有了轻量级自增锁,性能好很多。

3. 行级锁——"精准制导"

InnoDB 才支持行锁,MyISAM 是不行的——这也是一般项目都选 InnoDB 的核心原因之一。

行锁由存储引擎层实现,开销比较大(每行都要维护锁信息),但并发度高。行锁下面还有细分,这是面试重灾区,我们稍后展开。


二、按读写属性分——共享锁 vs 排他锁

这俩概念贯穿 MySQL 所有锁类型,先搞明白它们:

类型 别称 S/X 缩写 一句话
共享锁(Shared Lock) 读锁 S 锁 你能读,我也能读,但谁也不能写
排他锁(Exclusive Lock) 写锁 / 互斥锁 X 锁 我独占,别人读也不行、写也不行

兼容矩阵(这是面试里能画出来的加分项):

S 锁 X 锁
S 锁 ✅ 兼容 ❌ 冲突
X 锁 ❌ 冲突 ❌ 冲突
-- 加共享锁(S 锁)
SELECT * FROM user WHERE id = 1 LOCK IN SHARE MODE;  -- MySQL 8.0 改名了:
SELECT * FROM user WHERE id = 1 FOR SHARE;            -- 推荐写法

-- 加排他锁(X 锁)
SELECT * FROM user WHERE id = 1 FOR UPDATE;

一个很实用的面试小口诀:S 锁可以共存,X 锁六亲不认。


三、行级锁的细分类——面试高发区

这里是面试官最爱深挖的地方,也是 间隙锁文档 中详细讲过的内容。这里做个整体梳理:

行级锁
├── Record Lock(记录锁)      → 锁住一条具体的索引记录
├── Gap Lock(间隙锁)         → 锁住记录之间的空隙,防插入
├── Next-Key Lock(临键锁)    → Record + Gap,前开后闭区间
└── Insert Intention Lock     → 插入意向锁,特殊的间隙锁
Record Lock(记录锁)——最老实的那种
-- 锁住 id=10 这一行
SELECT * FROM user WHERE id = 10 FOR UPDATE;

锁住索引上的一条记录。如果表没有索引,InnoDB 会隐式创建聚簇索引来做行锁。

注意:行锁是加在索引上的,不是加在数据行上的。这句话面试说出来,直接上分。

Gap Lock(间隙锁)——防止幻读的功臣

锁住索引记录之间的空隙,防止别人在这些空隙里插入数据。

间隙锁只在 RR(可重复读) 隔离级别下生效。RC 级别下间隙锁直接退场。详情见 上一篇文章

Next-Key Lock(临键锁)——InnoDB 的默认行锁
数据分布:  1          5          10         15
索引:   ●——(1,5)——●——(5,10)——●——(10,15)——●——(15,+∞)

Next-Key Lock = 区间 + 右端点记录
          (1,5]      (5,10]     (10,15]    (15,+∞]

默认情况下,InnoDB 加的锁都是 Next-Key Lock。在某些情况下会退化:

  • 唯一索引等值查询且记录存在 → 退化为 Record Lock
  • 唯一索引等值查询且记录不存在 → 退化为 Gap Lock
Insert Intention Lock(插入意向锁)——隐形的排队哨兵

这是一种特殊的间隙锁。当你要往某个间隙插入数据时,会先在这个间隙上加一个"插入意向锁"。它跟间隙锁的规则是:

多个事务在同一个间隙加插入意向锁 → 互不冲突 插入意向锁遇到间隙锁 → 阻塞

说白了,插入意向锁的作用是让同间隙的插入操作能并发,但又保证在有人锁住整个间隙时插不进去。


四、按锁定方式分——隐式锁 vs 显式锁

类型 触发方式 示例
显式锁 你手动加的 SELECT ... FOR UPDATELOCK TABLES
隐式锁 MySQL 自动加的 DML 语句自动加行级 X 锁、MDL 锁、自增锁等

大多数时候你不需要操心隐式锁,MySQL 替你打理得很好。但了解它有个好处——排查线上锁问题的时候,你知道那些"看不见但确实存在的锁"在哪。


五、一张全景脑图

MySQL 锁体系
│
├── 按粒度分层
│   ├── 全局锁(FLUSH TABLES WITH READ LOCK)
│   ├── 表级锁
│   │   ├── 表锁(LOCK TABLES ... READ/WRITE)
│   │   ├── 元数据锁 MDL(自动,隐式)
│   │   └── 自增锁 AUTO-INC(自动,隐式)
│   └── 行级锁(InnoDB)
│       ├── Record Lock(记录锁)
│       ├── Gap Lock(间隙锁)
│       ├── Next-Key Lock(临键锁,默认)
│       └── Insert Intention Lock(插入意向锁)
│
└── 按读写属性分
    ├── 共享锁(S 锁,读锁)
    └── 排他锁(X 锁,写锁)

六、常见面试追问

Q1:InnoDB 行锁是加在数据行上吗?

不是。行锁是加在索引上的。如果你的一条 UPDATE 语句的 WHERE 条件不走索引,那 InnoDB 没办法精准定位到具体行,就会锁住整张表的所有行(实际是锁住所有行的聚簇索引记录),效果等同于表锁。

这也是为什么不走索引的 DML 在 RR 级别下尤其危险。

Q2:MyISAM 和 InnoDB 在锁上的核心区别?

MyISAM InnoDB
行级锁 ❌ 不支持 ✅ 支持
事务 ❌ 不支持 ✅ 支持
默认锁 表锁 行锁(Next-Key Lock)
读-读 不阻塞 不阻塞
读-写 写优先,读被阻塞 通过 MVCC 读不阻塞写

一句话:InnoDB 能活到今天是靠 MVCC + 行锁这套组合拳。

Q3:MySQL 怎么检测死锁?

InnoDB 内部维护了一个等待图(Wait-For Graph),当节点间形成环时即死锁。InnoDB 会主动检测并回滚其中一个事务。可以通过 SHOW ENGINE INNODB STATUS\G 查看最近的死锁信息。


七、一句话总结

MySQL 的锁,从上到下管全局、管表、管行;从读写属性分共享和排他;行锁再细分记录、间隙、临键、插入意向。记住这个三层结构,任何锁问题都能往里套。

Logo

汇聚全球AI编程工具,助力开发者即刻编程。

更多推荐