MySQL 的锁机制
1. 锁的作用与分类
锁的核心目标是解决并发事务中的资源竞争问题,防止数据不一致。MySQL 的锁可以从多个维度分类:
1.1 按锁的粒度(锁定范围)
锁类型 | 描述 | 适用引擎 | 特点 |
---|---|---|---|
表级锁 | 锁定整张表,其他事务无法修改表结构或数据。 | MyISAM、InnoDB | 开销小,并发性低 |
行级锁 | 仅锁定某一行或多行数据,其他行仍可访问。 | InnoDB | 开销大,并发性高 |
页级锁 | 锁定数据页(一组连续的记录),介于表锁和行锁之间。 | BDB | 较少使用 |
1.2 按锁的模式(访问权限)
锁类型 | 描述 | 兼容性 |
---|---|---|
共享锁 (S Lock) | 允许事务读取数据,其他事务可加共享锁,但不可加排他锁。 | 共享锁之间兼容 |
排他锁 (X Lock) | 允许事务修改数据,其他事务无法加任何锁(包括共享锁和排他锁)。 | 与所有锁冲突 |
1.3 按锁的实现方式
锁类型 | 描述 |
---|---|
悲观锁 | 默认认为并发冲突会发生,先加锁再操作(如 SELECT ... FOR UPDATE )。 |
乐观锁 | 假设冲突较少,通过版本号或时间戳控制(需应用层实现,如 CAS 机制)。 |
2. InnoDB 的锁机制
InnoDB 是 MySQL 默认支持事务的存储引擎,其锁机制最为复杂和常用。
2.1 行级锁
-
共享锁 (S Lock)
语法:SELECT ... LOCK IN SHARE MODE
用途:允许其他事务读,但禁止修改或加排他锁。 -
排他锁 (X Lock)
语法:SELECT ... FOR UPDATE
或 DML 语句(如UPDATE
,DELETE
)
用途:禁止其他事务读(取决于隔离级别)或修改。
示例:
-- 事务 A 对 id=1 的行加排他锁
BEGIN;
SELECT * FROM users WHERE id = 1 FOR UPDATE;
-- 事务 B 尝试修改会被阻塞
UPDATE users SET name = 'Bob' WHERE id = 1; -- 等待事务 A 提交或回滚
2.2 意向锁(Intention Locks)
- 意向共享锁 (IS Lock):事务打算在某些行上加共享锁,需先在表上加 IS 锁。
- 意向排他锁 (IX Lock):事务打算在某些行上加排他锁,需先在表上加 IX 锁。
作用:快速判断表级锁是否冲突,避免逐行检查。
2.3 间隙锁(Gap Locks)
- 锁定范围:索引记录的间隙(如
WHERE id BETWEEN 10 AND 20
),防止其他事务在此范围内插入数据。 - 用途:解决幻读问题(在
REPEATABLE READ
隔离级别下生效)。
示例:
-- 事务 A 锁定 id > 10 的间隙
SELECT * FROM users WHERE id > 10 FOR UPDATE;
-- 事务 B 插入 id=15 会被阻塞
INSERT INTO users (id, name) VALUES (15, 'Charlie');
2.4 临键锁(Next-Key Locks)
- 组成:行锁 + 间隙锁,锁定索引记录及其前面的间隙。
- 默认行为:InnoDB 在
REPEATABLE READ
隔离级别下使用临键锁。
2.5 插入意向锁(Insert Intention Locks)
- 一种特殊的间隙锁,表示事务打算在某个间隙插入数据。
- 允许多个事务在同一个间隙插入不同的数据(只要不冲突)。
3. 锁的兼容性
不同锁之间的兼容性如下表:
当前锁 \ 请求锁 | 共享锁 (S) | 排他锁 (X) | 意向共享 (IS) | 意向排他 (IX) |
---|---|---|---|---|
共享锁 (S) | ✔️ | ❌ | ✔️ | ❌ |
排他锁 (X) | ❌ | ❌ | ❌ | ❌ |
意向共享 (IS) | ✔️ | ❌ | ✔️ | ✔️ |
意向排他 (IX) | ❌ | ❌ | ✔️ | ✔️ |
4. 死锁(Deadlock)
4.1 死锁的产生
两个或多个事务互相等待对方释放锁,形成循环依赖。
示例:
- 事务 A 锁定行 1,请求行 2。
- 事务 B 锁定行 2,请求行 1。
- 双方互相等待,导致死锁。
4.2 死锁检测与处理
- 自动检测:InnoDB 使用等待图(Wait-for Graph)检测死锁。
- 处理方式:回滚代价最小的事务,另一个事务继续执行。
- 查看死锁日志:
SHOW ENGINE INNODB STATUS;
4.3 避免死锁的方法
- 保持事务短小,尽快提交或回滚。
- 按固定顺序访问资源(如按主键排序更新)。
- 使用索引减少锁范围。
- 设置合理的隔离级别(如
READ COMMITTED
减少间隙锁)。
5. 锁的监控
5.1 查看当前锁信息
-- 查看 InnoDB 锁状态
SHOW ENGINE INNODB STATUS;-- 通过系统表查询锁信息(MySQL 5.7+)
SELECT * FROM information_schema.INNODB_LOCKS;
SELECT * FROM information_schema.INNODB_LOCK_WAITS;
5.2 监控锁等待
-- 查看正在等待锁的事务
SELECT * FROM information_schema.INNODB_TRX
WHERE trx_state = 'LOCK WAIT';
6. 锁的优化与实践
6.1 减少锁冲突
- 合理设计索引:缩小锁范围(如使用唯一索引)。
- 避免长事务:尽快提交事务,减少锁持有时间。
- 使用低隔离级别:如
READ COMMITTED
减少间隙锁的使用。
6.2 显式锁的使用场景
- 悲观锁:高并发写场景(如库存扣减)。
BEGIN; SELECT stock FROM products WHERE id = 100 FOR UPDATE; UPDATE products SET stock = stock - 1 WHERE id = 100; COMMIT;
- 乐观锁:低冲突场景(通过版本号控制)。
-- 更新时检查版本号 UPDATE products SET stock = stock - 1, version = version + 1 WHERE id = 100 AND version = 5;
7. 不同存储引擎的锁差异
存储引擎 | 锁机制 | 特点 |
---|---|---|
InnoDB | 支持行锁、表锁 | 默认行级锁,支持事务和 MVCC |
MyISAM | 仅支持表锁 | 读写互斥,并发性能差 |
Memory | 表锁 | 数据存储在内存,锁竞争较少 |
8. 总结
MySQL 的锁机制是保障数据一致性和高并发的核心,理解以下关键点至关重要:
- 锁的粒度:行级锁 vs 表级锁。
- 锁的模式:共享锁、排他锁、意向锁。
- 锁的兼容性:不同锁之间的冲突关系。
- 死锁处理:检测、回滚与避免策略。
- 优化实践:索引设计、事务时长控制、隔离级别选择。
通过合理使用锁机制,可以在高并发场景下平衡数据一致性和系统性能。