锁机制
2026/1/15大约 4 分钟
锁机制
锁的分类
按粒度分
| 类型 | 说明 | 特点 |
|---|---|---|
| 全局锁 | 锁整个数据库 | 用于全库备份 |
| 表级锁 | 锁整张表 | 开销小,并发低 |
| 行级锁 | 锁单行数据 | 开销大,并发高 |
按模式分
| 类型 | 说明 |
|---|---|
| 共享锁(S锁) | 读锁,多个事务可同时持有 |
| 排他锁(X锁) | 写锁,只能一个事务持有 |
全局锁
-- 加全局读锁
FLUSH TABLES WITH READ LOCK;
-- 释放锁
UNLOCK TABLES;使用场景
全库逻辑备份:
# 使用 mysqldump 备份
mysqldump --single-transaction -uroot -p database > backup.sql表级锁
表锁
-- 加表读锁
LOCK TABLES t READ;
-- 加表写锁
LOCK TABLES t WRITE;
-- 释放锁
UNLOCK TABLES;元数据锁(MDL)
自动加锁,保护表结构。
| 操作 | MDL 锁类型 |
|---|---|
| SELECT/DML | MDL 读锁 |
| DDL | MDL 写锁 |
注意
DDL 操作会阻塞所有 DML 操作,在线上执行 DDL 要谨慎。
意向锁
表级锁,用于快速判断表中是否有行锁。
| 类型 | 说明 |
|---|---|
| 意向共享锁(IS) | 事务想获取行的 S 锁 |
| 意向排他锁(IX) | 事务想获取行的 X 锁 |
行级锁
InnoDB 支持行级锁,MyISAM 不支持。
记录锁(Record Lock)
锁定单行记录。
-- 加 S 锁
SELECT * FROM t WHERE id = 1 LOCK IN SHARE MODE;
-- MySQL 8.0+
SELECT * FROM t WHERE id = 1 FOR SHARE;
-- 加 X 锁
SELECT * FROM t WHERE id = 1 FOR UPDATE;间隙锁(Gap Lock)
锁定索引记录之间的间隙,防止幻读。
-- 假设表中有 id = 1, 5, 10 的记录
SELECT * FROM t WHERE id = 3 FOR UPDATE;
-- 锁定 (1, 5) 的间隙临键锁(Next-Key Lock)
记录锁 + 间隙锁,锁定记录及其前面的间隙。
-- 假设表中有 id = 1, 5, 10 的记录
SELECT * FROM t WHERE id = 5 FOR UPDATE;
-- 锁定 (1, 5] 的范围行锁加锁规则
- 加锁的基本单位是 Next-Key Lock
- 查询过程中访问到的对象才会加锁
- 等值查询,唯一索引,Next-Key Lock 退化为记录锁
- 等值查询,非唯一索引,向右遍历到不满足条件时,退化为间隙锁
- 范围查询,唯一索引,会访问到不满足条件的第一个值为止
示例分析
-- 表结构
CREATE TABLE t (
id INT PRIMARY KEY,
c INT,
d INT,
INDEX idx_c(c)
);
-- 数据:(1,1,1), (5,5,5), (10,10,10), (15,15,15)
-- 示例1:唯一索引等值查询
SELECT * FROM t WHERE id = 5 FOR UPDATE;
-- 只锁 id=5 这一行(记录锁)
-- 示例2:唯一索引等值查询(不存在的值)
SELECT * FROM t WHERE id = 7 FOR UPDATE;
-- 锁 (5, 10) 间隙(间隙锁)
-- 示例3:非唯一索引等值查询
SELECT * FROM t WHERE c = 5 FOR UPDATE;
-- 锁 (1, 5]、(5, 10)(Next-Key Lock + 间隙锁)
-- 示例4:范围查询
SELECT * FROM t WHERE id >= 10 AND id < 15 FOR UPDATE;
-- 锁 [10, 15)(Next-Key Lock)死锁
什么是死锁
两个或多个事务互相等待对方持有的锁。
死锁示例
-- 事务1
BEGIN;
UPDATE t SET d = d + 1 WHERE id = 1;
-- 等待事务2释放 id=2 的锁
UPDATE t SET d = d + 1 WHERE id = 2;
-- 事务2
BEGIN;
UPDATE t SET d = d + 1 WHERE id = 2;
-- 等待事务1释放 id=1 的锁
UPDATE t SET d = d + 1 WHERE id = 1;死锁检测
-- 查看死锁日志
SHOW ENGINE INNODB STATUS;
-- 死锁检测参数
innodb_deadlock_detect = ON -- 开启死锁检测(默认)
innodb_lock_wait_timeout = 50 -- 锁等待超时时间避免死锁
- 固定加锁顺序:所有事务按相同顺序访问资源
- 减少锁持有时间:尽快提交事务
- 降低隔离级别:使用 RC 级别减少间隙锁
- 使用合理的索引:避免全表扫描
乐观锁 vs 悲观锁
悲观锁
假设会发生冲突,先加锁再操作。
-- 使用 FOR UPDATE 加悲观锁
BEGIN;
SELECT * FROM t WHERE id = 1 FOR UPDATE;
UPDATE t SET stock = stock - 1 WHERE id = 1;
COMMIT;乐观锁
假设不会发生冲突,通过版本号检测冲突。
-- 使用版本号实现乐观锁
-- 1. 查询数据和版本号
SELECT id, stock, version FROM t WHERE id = 1;
-- 假设 stock=10, version=1
-- 2. 更新时检查版本号
UPDATE t SET stock = stock - 1, version = version + 1
WHERE id = 1 AND version = 1;
-- 3. 检查影响行数,为0表示冲突对比
| 特性 | 悲观锁 | 乐观锁 |
|---|---|---|
| 实现方式 | 数据库锁 | 版本号/CAS |
| 并发性能 | 低 | 高 |
| 适用场景 | 写多读少 | 读多写少 |
| 冲突处理 | 阻塞等待 | 重试 |
锁监控
-- 查看当前锁等待
SELECT * FROM information_schema.innodb_lock_waits;
-- 查看当前锁
SELECT * FROM performance_schema.data_locks;
-- 查看锁等待的事务
SELECT * FROM information_schema.innodb_trx;
-- 查看锁等待超时参数
SHOW VARIABLES LIKE 'innodb_lock_wait_timeout';