在 MySQL 面试中,锁机制是高频考点之一。无论是日常开发中的并发控制,还是面试中对事务隔离、死锁、性能优化的追问,都绕不开锁这个话题。很多候选人对“共享锁”“排他锁”“乐观锁”“悲观锁”这些名词耳熟能详,但一旦被追问“它们分别解决什么问题”“在 InnoDB 中如何实现”“什么时候该用哪一种”,回答往往不够清晰。本文将从概念、实现方式、适用场景和常见误区四个维度,系统梳理 MySQL 中的这几类锁,帮助你在面试中给出有深度的回答。

一、为什么需要锁?
数据库锁的核心目标是在并发环境下保证数据的一致性和完整性。当多个事务同时读写同一份数据时,如果没有协调机制,就可能出现脏读、不可重复读、幻读以及更新丢失等问题。锁就是用来协调并发访问、控制事务之间相互影响的手段。
需要注意的是,锁的粒度和策略直接影响系统的并发性能。锁得太粗,并发度低;锁得太细,管理开销大,还容易死锁。因此,理解不同锁类型的定位,比死记定义更重要。
二、共享锁与排他锁:按兼容性划分
共享锁(Shared Lock,S 锁)和排他锁(Exclusive Lock,X 锁)是按锁的兼容性来划分的,这是 InnoDB 中最基础的两种行级锁。
1. 共享锁(S 锁)
共享锁又称读锁。当一个事务对某行数据加上 S 锁后:
- 其他事务可以继续读取该行,并加 S 锁;
- 其他事务不能对该行加 X 锁,即不能修改;
- 直到持有 S 锁的事务释放锁为止。
在 InnoDB 中,可以通过以下语句显式加共享锁:
SELECT * FROM users WHERE id = 1 LOCK IN SHARE MODE;
-- MySQL 8.0 中推荐写法:
SELECT * FROM users WHERE id = 1 FOR SHARE;
普通的一致性读(不加锁的 SELECT)在 MVCC 机制下读取快照,不涉及 S 锁。只有显式加锁读或某些特定场景(如外键检查)才会使用 S 锁。
2. 排他锁(X 锁)
排他锁又称写锁。当一个事务对某行加上 X 锁后:
- 其他事务不能读取(在加锁读的意义上)该行,也不能加任何锁;
- 只有持有 X 锁的事务可以读写该行。
显式加排他锁的写法:
SELECT * FROM users WHERE id = 1 FOR UPDATE;
此外,INSERT、UPDATE、DELETE 操作会自动对涉及的行加 X 锁。
3. 兼容性矩阵
| 当前锁 \ 请求锁 | S 锁 | X 锁 |
|---|---|---|
| S 锁 | 兼容 | 冲突 |
| X 锁 | 冲突 | 冲突 |
一句话总结:S 锁与 S 锁兼容,S 锁与 X 锁冲突,X 锁与 X 锁冲突。
面试中常被追问:“共享锁和排他锁是行锁还是表锁?” 准确回答是:InnoDB 中它们既可以是行级锁,也可以是表级锁(如意向锁 IS/IX 就是表级锁),具体取决于加锁的粒度和语句。InnoDB 默认使用行级锁,并通过意向锁来实现行锁与表锁的共存。
三、乐观锁与悲观锁:按并发策略划分
如果说 S 锁和 X 锁是“数据库层面提供的具体锁”,那么乐观锁和悲观锁更像是并发控制的两种思想或策略。它们不是 MySQL 内置的锁类型,而是开发者根据业务场景选择的实现方式。
1. 悲观锁(Pessimistic Locking)
悲观锁的核心假设是:并发冲突很可能发生。因此,在读取数据时就先加锁,确保在自己操作完成之前,别人无法修改。
在 MySQL 中,悲观锁通常通过 FOR UPDATE 或 LOCK IN SHARE MODE 实现,依赖数据库的行锁机制。典型场景:
START TRANSACTION;
SELECT stock FROM products WHERE id = 100 FOR UPDATE;
-- 业务判断与扣减
UPDATE products SET stock = stock - 1 WHERE id = 100;
COMMIT;
优点:实现简单,数据一致性强,适合写多读少、冲突频繁的场景。
缺点:加锁会带来开销,容易造成锁等待甚至死锁;高并发下吞吐量下降。
2. 乐观锁(Optimistic Locking)
乐观锁的核心假设是:并发冲突不常发生。因此,它不在读取时加锁,而是在更新时检查数据是否被他人修改过。如果被修改过,则更新失败,由业务决定重试或报错。
最常见的实现方式是版本号机制或时间戳机制:
-- 表中增加 version 字段
UPDATE products
SET stock = stock - 1, version = version + 1
WHERE id = 100 AND version = 5;
如果受影响行数为 0,说明在读取到更新之间,数据已被其他事务修改,当前操作需要重试。
优点:不加锁,读操作不受影响,并发性能好,适合读多写少、冲突较少的场景。
缺点:失败需要重试,业务逻辑更复杂;在高冲突场景下重试成本高,反而降低性能。
3. 对比与选择
| 维度 | 悲观锁 | 乐观锁 |
|---|---|---|
| 加锁时机 | 读取时加锁 | 更新时检查 |
| 实现方式 | FOR UPDATE 等 |
版本号、时间戳、CAS |
| 适用场景 | 写多读少、冲突高 | 读多写少、冲突低 |
| 并发性能 | 较低 | 较高 |
| 实现复杂度 | 低 | 较高(需处理重试) |
面试中如果被问到“你项目中用哪种锁”,不要只回答名称,而应结合业务说明:例如库存扣减若并发极高,可考虑悲观锁保证强一致;若商品详情页的浏览量统计,则乐观锁或原子更新更合适。
四、常见误区与面试追问
误区一:乐观锁是 MySQL 提供的锁。
实际上,乐观锁不是数据库锁,而是应用层的并发控制策略,通常借助版本号字段实现。
误区二:加了共享锁就不能读。
共享锁之间兼容,多个事务可以同时持有 S 锁进行读取,只是不能写。
误区三:SELECT 一定会加锁。
普通 SELECT 在 InnoDB 中走 MVCC 快照读,不加锁;只有 FOR UPDATE、FOR SHARE 或串行化隔离级别下才会加锁。
常见追问:乐观锁和悲观锁哪个性能更好?
没有绝对答案。低冲突场景下乐观锁性能更好,高冲突场景下悲观锁反而更稳定。选择取决于冲突概率、重试成本和一致性要求。
常见追问:S 锁和 X 锁会导致死锁吗?
会。例如两个事务分别持有不同行的 X 锁,又互相请求对方持有的行锁,就会形成死锁。InnoDB 有死锁检测机制,会回滚其中一个事务。
五、总结
MySQL 中的锁可以从两个角度理解:
- 按兼容性:共享锁(S 锁)用于读,排他锁(X 锁)用于写,二者兼容性不同,是 InnoDB 行级锁的基础。
- 按并发策略:悲观锁在读取时就加锁,适合高冲突场景;乐观锁在更新时校验版本,适合低冲突场景。
面试中回答这类问题,建议遵循“定义 → 实现 → 场景 → 取舍”的结构,既能体现知识广度,也能展现工程判断力。锁不是越多越好,而是要在一致性、并发性和复杂度之间找到平衡点。理解这一点,才算真正掌握了 MySQL 锁的精髓。
未经允许不得转载:任鹏个人博客 » MySQL 中的锁类型:共享锁、排他锁、乐观锁与悲观锁

