7802_Mysql数据库锁分类
MySQL 的锁可以按照不同的维度进行分类,下图清晰地展示了其锁的层次结构:
flowchart TD
A[MySQL 锁] --> B["按粒度分<br>(Granularity)"]
A --> C["按模式分<br>(Mode)"]
A --> D["按思想分<br>(Philosophy)"]
B --> B1[全局锁]
B --> B2[表级锁]
B --> B3[行级锁]
B2 --> B2_1[表锁 Table Lock]
B2 --> B2_2[元数据锁 MDL]
B2 --> B2_3[意向锁 Intention Lock]
B3 --> B3_1[记录锁 Record Lock]
B3 --> B3_2[间隙锁 Gap Lock]
B3 --> B3_3[临键锁 Next-Key Lock]
B3 --> B3_4[插入意向锁 Insert Intention Lock]
C --> C1[共享锁 S Lock]
C --> C2[排他锁 X Lock]
D --> D1[悲观锁]
D --> D2[乐观锁]
接下来,我们详细解读图中的每一种锁。
一、按锁的粒度分类
锁的粒度指的是锁定的数据范围大小。粒度越小,并发度越高,但管理越复杂。
1. 全局锁
- 作用范围:整个数据库实例。
- 实现方式:使用命令
FLUSH TABLES WITH READ LOCK(FTWRL)。 - 效果:使整个数据库处于只读状态。所有数据更新语句(DML)、数据定义语句(DDL)和更新类事务的提交语句都会被阻塞。
- 用途:主要用于全库逻辑备份。但请注意,在支持事务的引擎(如 InnoDB)中,更推荐使用
mysqldump --single-transaction来获得一致性视图,而不需要锁定整个库。
2. 表级锁
- 作用范围:整张表。
- 种类:
- 表锁 (Table Lock):
- 通过
LOCK TABLES table_name READ/WRITE手动加锁。 - 读锁(共享锁):阻止其他会话写入,但允许读取。
- 写锁(排他锁):阻止其他会话的读写。
- 开销小,加锁快;但粒度大,并发度低。InnoDB 很少使用。
- 通过
- 元数据锁 (Metadata Lock, MDL):
- MySQL 5.5 引入,用于保证读写的正确性。
- 自动加锁,无需手动操作。
- 当对一个表做 CRUD 操作(DML)时,加 MDL 读锁;当要对表做结构变更操作(DDL)时,加 MDL 写锁。
- 读锁之间不互斥,多个线程可以同时读。
- 读写锁、写锁之间互斥,DDL 操作会阻塞并发的 CRUD 操作,反之亦然。
- 意向锁 (Intention Lock):
- InnoDB 特有,是一种表级锁,但它标志着事务稍后会对表中的行施加哪种类型的锁。
- 目的:为了高效协调表锁和行锁的冲突。例如,当你想给一个表加表写锁时,数据库需要检查是否有其他事务已经持有了该表中某行的行锁。意向锁使得这个过程不需要逐行检查。
- 意向共享锁 (IS):事务打算给某些行加共享锁(S Lock)。
- 意向排他锁 (IX):事务打算给某些行加排他锁(X Lock)。
- 冲突矩阵:
请求锁类型 IS IX S X IS 兼容 兼容 兼容 冲突 IX 兼容 兼容 冲突 冲突 S 兼容 冲突 兼容 冲突 X 冲突 冲突 冲突 冲突
- 表锁 (Table Lock):
3. 行级锁
MYSQL的数据:
--表结构
CREATE TABLE `users` (
`id` bigint(20) NOT NULL COMMENT '用户ID',
`code` varchar(100) DEFAULT NULL COMMENT '用户编号',
`name` varchar(100) DEFAULT NULL COMMENT '用户姓名',
PRIMARY KEY (`id`),
KEY `users_code_IDX` (`code`) USING BTREE
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;
--数据
INSERT INTO users(id, code, name)
VALUES(1, '0001', '张三');
INSERT INTO users(id, code, name)
VALUES(10, '0010', '李四');
INSERT INTO users(id, code, name)
VALUES(20, '0020', '王五');
INSERT INTO users(id, code, name)
VALUES(100, '0100', '赵六');
- 作用范围:单行或多行记录。
- 说明:InnoDB 引擎支持的行级锁是基于索引实现的。如果查询没有用到索引,会退化为表锁。
-
例如,
SELECT * FROM users WHERE name='小王' for update;因为没有命中索引,所以会锁住全表,阻止 id=2 的新记录插入。用户1: -- 开始事务 START TRANSACTION; -- 执行一些 SQL 语句 SELECT * FROM users WHERE name='小王' for update; 用户2: INSERT INTO users(id, code, name) VALUES(2, '0002', '小李');
-
- 种类:
-
记录锁 (Record Lock):
- 锁住索引的一条具体记录。
- 防止其他事务对此条记录进行修改或删除。
- 例如,
SELECT * FROM users WHERE id=10 for update;会锁住 id=10的记录,阻止 id=10 的记录更新。
用户1: -- 开始事务 START TRANSACTION; -- 执行一些 SQL 语句 SELECT * FROM users WHERE id=10 for update; 用户2: update users set name='李四1' where id=10; -
间隙锁 (Gap Lock):
- 锁住索引记录之间的间隙(一个开区间),防止其他事务在这个间隙中插入新的记录。
- 解决了“幻读”问题。
- 例如,
SELECT * FROM users WHERE id BETWEEN 10 AND 20 for update;会锁住 id=10 到 id=20 之间的所有间隙,阻止 id=15 的新记录插入。
用户1: -- 开始事务 START TRANSACTION; -- 执行一些 SQL 语句 SELECT * FROM users WHERE id =15 for update; 用户2: INSERT INTO users(id, code, name) VALUES(15, '0015', '王八'); -
临键锁 (Next-Key Lock):
- 记录锁 + 间隙锁 的组合。它既锁住记录本身,也锁住该记录之前的间隙。
- 它是一个左开右闭的区间。例如,假设数据库中有索引记录 1, 10, 20, 100,那么临键锁可能锁定的区间有:
(-∞, 1],(1, 10],(10, 20],(20, +∞)。 - 这是 InnoDB 默认的行锁算法,它同时解决了“不可重复读”和“幻读”的问题。
- 例如,
SELECT * FROM users WHERE code='0020' for update;会锁住(10, 20]间隙,阻止 id=11 的新记录插入。
用户1: -- 开始事务 START TRANSACTION; -- 执行一些 SQL 语句 SELECT * FROM users WHERE code='0020' for update; 用户2: INSERT INTO users(id, code, name) VALUES(11, '0011', '小李');
-
二、按锁的模式分类
1. 共享锁 (Shared Lock, S Lock)
- 又称为“读锁”。
- 允许多个事务同时读取同一资源。
- 如果一个事务持有了共享锁,其他事务可以继续加共享锁,但不能加排他锁。
- 手动加锁:
SELECT ... LOCK IN SHARE MODE;(旧语法) 或SELECT ... FOR SHARE;(MySQL 8.0+)。
2. 排他锁 (Exclusive Lock, X Lock)
- 又称为“写锁”。
- 允许持有锁的事务更新或删除数据。
- 如果一个事务持有了排他锁,其他事务不能再加任何类型的锁(共享锁或排他锁),直到它释放。
- 手动加锁:
SELECT ... FOR UPDATE; - InnoDB 会在执行 UPDATE、DELETE 语句时自动给涉及的行加排他锁。
三、按锁的思想分类(非数据库内置,是应用策略)
1. 悲观锁
- 思想:总是假设最坏的情况,认为拿数据的时候别人会修改,所以每次操作数据时都会上锁,这样别人想拿这个数据就会阻塞直到它拿到锁。
- 实现:依靠数据库自身的锁机制实现,例如
SELECT ... FOR UPDATE。 - 适用场景:写多读少,竞争激烈的场景。
2. 乐观锁
- 思想:总是假设最好的情况,认为拿数据的时候别人不会修改,所以不上锁。但在更新的时候会判断一下在此期间别人有没有去更新这个数据。
- 实现:不在数据库中加锁,而是在表中增加一个版本号(
version)字段或时间戳。更新时,检查版本号是否和之前读取的一致。UPDATE table_name SET column = new_value, version = current_version + 1 WHERE id = #{id} AND version = #{old_version}; - 适用场景:读多写少,竞争不激烈的场景。
总结
| 锁类型 | 主要特点 | 用途 | 支持引擎 |
|---|---|---|---|
| 行级锁 | 粒度小,并发高,管理复杂 | 解决事务间的行级冲突 | InnoDB |
| 表级锁 | 粒度大,并发低,管理简单 | 全表操作、DDL 协调 | 所有引擎 |
| 全局锁 | 锁定整个库 | 全库备份 | 所有引擎 |
| 共享锁 (S) | 允许读,阻止写 | 保证读取一致性 | InnoDB |
| 排他锁 (X) | 阻止读写 | 保证更新排他性 | InnoDB |
| 悲观锁 | 先加锁,后操作 | 高竞争写环境 | 通过 DB 锁实现 |
| 乐观锁 | 不加锁,更新时校验 | 低竞争写环境 | 应用层实现 |
理解这些锁的区别和适用场景,对于设计高性能、高并发的数据库应用以及排查解决锁冲突问题至关重要。在 InnoDB 中,最常用和最需要重点关注的是行级锁和元数据锁 (MDL)。

浙公网安备 33010602011771号