一、锁的本质与分类
在InnoDB存储引擎中,锁的核心目的是解决并发事务中的数据一致性问题。根据锁定范围可分为:
- 行级锁:记录锁、间隙锁、邻键锁
- 表级锁:意向锁、自增锁等
二、三种行级锁的底层原理
1. 记录锁(Record Lock)
- 定义 :锁定索引中的具体某一行记录
- 触发条件:
sql
SELECT * FROM table WHERE id = 10 FOR UPDATE; -- 锁定id=10的行
-
实现机制:
- 当使用主键或唯一索引时,直接锁定目标行
- 若索引不存在,退化为表锁(全表扫描场景)
2. 间隙锁(Gap Lock)
- 定义 :锁定索引记录之间的间隙区间(左开右开区间)
- 触发条件:
sql
SELECT * FROM table WHERE age BETWEEN 20 AND 30 FOR UPDATE; -- 锁定(20,30)的间隙
-
核心作用:
- 防止其他事务在间隙中插入新数据(解决幻读)
- 仅存在于REPEATABLE READ隔离级别
3. 邻键锁(Next-Key Lock)
- 定义 :记录锁 + 间隙锁的组合,锁定一个左开右闭区间
- 触发条件:
sql
SELECT * FROM table WHERE id > 15 FOR UPDATE; -- 锁定(15, +∞)
-
默认行为:
- InnoDB在REPEATABLE READ级别下默认使用邻键锁
- 锁定范围示例:
(5, 10]
表示锁定5到10之间的间隙,以及10这个记录
三、索引类型对锁的影响
1. 唯一索引 vs 非唯一索引
场景 | 唯一索引 | 非唯一索引 |
---|---|---|
等值查询 | 仅加记录锁 | 加邻键锁(防止幻读) |
范围查询 | 邻键锁 | 邻键锁 |
2. 示例分析
假设表users
结构:
sql
CREATE TABLE users (
id INT PRIMARY KEY, -- 主键(聚簇索引)
age INT NOT NULL,
KEY idx_age (age) -- 非唯一索引
);
数据分布:
diff
+----+-----+
| id | age |
+----+-----+
| 5 | 20 |
| 10 | 25 |
| 15 | 25 |
| 20 | 30 |
+----+-----+
场景1:等值查询(age=25)
sql
-- 事务A
SELECT * FROM users WHERE age = 25 FOR UPDATE;
-
锁定范围:
- 非唯一索引
idx_age
:锁定age=25
对应的所有记录(id=10和15) - 主键索引:对id=10和15加记录锁
- 间隙锁:锁定
(20,25)
和(25,30)
的间隙(防止插入age=25的新数据)
- 非唯一索引
场景2:范围查询(id>10)
sql
-- 事务B
SELECT * FROM users WHERE id > 10 FOR UPDATE;
锁定范围:
- 主键索引:
(10,15]
,(15,20]
,(20, +∞)
- 阻止插入id>10的任何新记录
1. 锁兼容矩阵
记录锁(X) | 间隙锁(GAP) | 邻键锁(X+GAP) | |
---|---|---|---|
记录锁 | ❌ | ✔️ | ❌ |
间隙锁 | ✔️ | ✔️ | ✔️ |
邻键锁 | ❌ | ✔️ | ❌ |
2. 经典死锁场景
sql
-- 事务A
BEGIN;
SELECT * FROM users WHERE age = 25 FOR UPDATE; -- 获取邻键锁
-- 事务B
BEGIN;
SELECT * FROM users WHERE age = 25 FOR UPDATE; -- 等待事务A释放锁
-- 事务A尝试插入冲突数据
INSERT INTO users VALUES (12, 25); -- 被事务B的间隙锁阻塞
此时形成相互等待,InnoDB会自动检测并回滚代价较小的事务。
五、锁监控与优化策略
1. 查看当前锁信息
sql
-- 查看锁等待
SHOW ENGINE INNODB STATUS\G
-- 查询锁详情
SELECT * FROM performance_schema.data_locks;
2. 性能优化建议
-
索引优化:
- 尽量使用覆盖索引,减少回表操作
- 避免全表扫描(会导致锁升级)
-
事务设计:
- 控制事务粒度(避免大事务)
- 在READ COMMITTED级别下间隙锁范围更小
-
隔离级别调整:
css
SET SESSION TRANSACTION ISOLATION LEVEL READ COMMITTED;
六、总结:锁机制与隔离级别的关系
隔离级别 | 锁策略 | 幻读风险 |
---|---|---|
READ UNCOMMITTED | 无锁(脏读) | 存在 |
READ COMMITTED | 记录锁为主 | 存在 |
REPEATABLE READ | 邻键锁(默认) | 不存在 |
SERIALIZABLE | 全表锁(悲观锁) | 不存在 |
总结 :
理解不同锁的工作机制,是优化高并发场景下MySQL性能的关键。通过合理设计索引、控制事务范围和选择隔离级别,可显著降低锁冲突概率。