MySQL: MySQL锁机制与MVCC原理
最后更新:2026-08-26
锁是并发控制的核心——理解锁机制才能写出高效的并发代码。
本课讲解 MySQL 的锁机制。
1. 你将学到
- 表锁 vs 行锁
- 共享锁(S锁)与排他锁(X锁)
- 意向锁
- MVCC 多版本并发控制
- 死锁检测与处理
2. 锁的分类
graph TB
A[MySQL 锁] --> B[按粒度]
A --> C[按类型]
B --> D[表锁 Table Lock]
B --> E[行锁 Row Lock]
B --> F[页锁 Page Lock]
C --> G[共享锁 S Lock]
C --> H[排他锁 X Lock]
C --> I[意向锁 Intention Lock]
| 维度 | 表锁 | 行锁 |
|---|---|---|
| 粒度 | 整张表 | 单行 |
| 冲突概率 | 高 | 低 |
| 并发性能 | 低 | 高 |
| 开销 | 小 | 大 |
| 引擎 | MyISAM/InnoDB | InnoDB |
3. 共享锁与排他锁
(1) 共享锁(S Lock)
SQL
-- 加共享锁(读锁)— MySQL 8.0+ 推荐语法
SELECT * FROM users WHERE id = 1 FOR SHARE;
-- MySQL 5.7 旧语法(8.0 已废弃但仍可用)
-- SELECT * FROM users WHERE id = 1 LOCK IN SHARE MODE;
-- 其他事务可以读,不能写
(2) 排他锁(X Lock)
SQL
-- 加排他锁(写锁)
SELECT * FROM users WHERE id = 1 FOR UPDATE;
-- 其他事务不能读(如果也加锁)也不能写
| 锁类型 | 读 | 写 |
|---|---|---|
| 共享锁 S | ✅ | ❌ |
| 排他锁 X | ❌ | ❌ |
4. 意向锁
意向锁是表级锁,用于快速判断表中是否有行锁。
| 意向锁 | 说明 |
|---|---|
| IS(意向共享锁) | 事务打算给行加 S 锁 |
| IX(意向排他锁) | 事务打算给行加 X 锁 |
作用: 加表锁时无需逐行检查,只需检查意向锁。
▶ 示例:锁的实际应用
SQL
-- 会话1:加排他锁
START TRANSACTION;
SELECT * FROM accounts WHERE id = 1 FOR UPDATE;
-- 此时 id=1 被锁住
-- 会话2:尝试更新(等待)
UPDATE accounts SET balance = 500 WHERE id = 1;
-- 等待直到会话1提交或超时
-- 会话1:提交
COMMIT;
-- 会话2立即执行成功
5. MVCC 多版本并发控制
MVCC 通过保存数据的多个版本,实现无锁读取。
(1) MVCC 原理
graph LR
A[当前数据] --> B[Undo Log<br/>历史版本链]
B --> C[v1] --> D[v2] --> E[v3]
F[Read View] -->|根据隔离级别| G[读取哪个版本]
(2) 不同隔离级别的读取行为
| 隔离级别 | 读取方式 |
|---|---|
| READ UNCOMMITTED | 总是读最新(可能脏读) |
| READ COMMITTED | 每次 SELECT 创建新 Read View |
| REPEATABLE READ | 事务开始时创建 Read View |
| SERIALIZABLE | 加锁读取 |
6. 死锁
(1) 什么是死锁
两个事务互相等待对方释放锁:
SQL
-- 事务A
START TRANSACTION;
UPDATE accounts SET balance = 100 WHERE id = 1; -- 锁住 id=1
UPDATE accounts SET balance = 200 WHERE id = 2; -- 等待 id=2
-- 事务B
START TRANSACTION;
UPDATE accounts SET balance = 300 WHERE id = 2; -- 锁住 id=2
UPDATE accounts SET balance = 400 WHERE id = 1; -- 等待 id=1(死锁!)
(2) 死锁处理
SQL
-- 查看死锁信息
SHOW ENGINE INNODB STATUS\G
-- 设置锁等待超时
SET innodb_lock_wait_timeout = 50; -- 秒
(3) 避免死锁
| 策略 | 说明 |
|---|---|
| 固定顺序 | 按相同顺序访问表和行 |
| 减少锁持有时间 | 事务尽快提交 |
| 使用低隔离级别 | READ COMMITTED 锁更少 |
| 合理建索引 | 减少锁范围 |
7. 锁等待超时
SQL
-- 查看锁等待超时
SELECT @@innodb_lock_wait_timeout;
-- 设置(秒)
SET innodb_lock_wait_timeout = 10;
-- 超时报错
-- ERROR 1205: Lock wait timeout exceeded
❓ 常见问题
Q InnoDB 用表锁还是行锁?
A 默认用行锁(基于索引)。没有索引时退化为表锁。
Q SELECT 会加锁吗?
A 普通 SELECT 不加锁(MVCC 快照读)。
SELECT ... FOR UPDATE 加排他锁,SELECT ... FOR SHARE(MySQL 8.0+)加共享锁。旧语法 LOCK IN SHARE MODE 在 8.0 中已废弃。Q 如何查看当前锁?
A
SELECT * FROM performance_schema.data_locks;(MySQL 8.0)Q InnoDB 用表锁还是行锁?
A 默认行锁,基于索引实现。无索引时退化为表锁。
Q SELECT 会加锁吗?
A 普通 SELECT 不加锁(MVCC 快照读),FOR UPDATE 加排他锁,FOR SHARE 加共享锁。
📖 小节
- 表锁粒度大、并发低,行锁粒度小、并发高
- 共享锁(S)允许多读,排他锁(X)独占写
- MVCC 通过多版本实现无锁读取
- 死锁是互相等待,MySQL 自动检测并回滚一个事务
- 避免死锁:固定访问顺序、减少锁持有时间
📝 作业
-
基础题(难度⭐):演示共享锁和排他锁的区别。
-
进阶题(难度⭐⭐):构造一个死锁场景并观察 MySQL 的处理。
-
挑战题(难度⭐⭐⭐):对比不同隔离级别下的并发读写行为。