InnoDB 锁体系详细讲解
InnoDB 锁可以理解为:数据库为了在多人同时读写时,既保证数据正确,又尽量提高并发性能而设计的一套交通规则。
1. 为什么数据库需要锁?
假设有一张账户表:
accounts
+----+--------+
| id | money |
+----+--------+
| 1 | 1000 |
+----+--------+
现在两个事务同时执行:
事务 A:
UPDATE accounts SET money = money - 100 WHERE id = 1;
事务 B:
UPDATE accounts SET money = money - 200 WHERE id = 1;
如果数据库不加控制,两个事务可能同时读到 money = 1000,然后分别写回:
- A 写成
900 - B 写成
800
最终可能只剩 800,看起来好像 A 的扣款丢了。
这类问题叫做 并发问题。
数据库用锁来避免这些问题。
2. 事务是什么?
理解 InnoDB 锁之前,先要知道事务。
事务是一组 SQL 操作,要么全部成功,要么全部失败。
START TRANSACTION;
UPDATE accounts SET money = money - 100 WHERE id = 1;
UPDATE accounts SET money = money + 100 WHERE id = 2;
COMMIT;
这表示转账:
- 账户 1 扣 100
- 账户 2 加 100
如果中间出错,就应该全部撤销。
事务有四个经典特性:ACID。
| 特性 | 含义 |
|---|---|
| A Atomicity 原子性 | 要么全做,要么全不做 |
| C Consistency 一致性 | 数据从一个正确状态变到另一个正确状态 |
| I Isolation 隔离性 | 多个事务之间互不干扰到一定程度 |
| D Durability 持久性 | 提交后数据永久保存 |
锁主要服务于 隔离性 和 一致性。
3. InnoDB 是什么?
MySQL 有不同的存储引擎,比如:
- InnoDB
- MyISAM
- Memory
现在最常用的是 InnoDB。
InnoDB 支持:
- 事务
- 行级锁
- 外键
- 崩溃恢复
- MVCC
所以我们讲 MySQL 锁,大多数时候其实是在讲 InnoDB 的锁。
4. InnoDB 锁体系的大图
InnoDB 的锁可以从几个角度理解:
InnoDB 锁体系
├── 按锁粒度
│ ├── 表锁
│ └── 行锁
│
├── 按锁模式
│ ├── 共享锁 S Lock
│ └── 排他锁 X Lock
│
├── 按行锁算法
│ ├── Record Lock 记录锁
│ ├── Gap Lock 间隙锁
│ └── Next-Key Lock 临键锁
│
├── 意向锁
│ ├── IS 意向共享锁
│ └── IX 意向排他锁
│
├── 特殊锁
│ ├── 插入意向锁
│ ├── 自增锁
│ └── 元数据锁 MDL
│
└── MVCC
├── 快照读
└── 当前读
这张图先不用全部记住。我们逐个拆。
一、共享锁和排他锁
5. 共享锁:S Lock
共享锁又叫 读锁。
含义是:我正在读这条数据,别人也可以读,但别人不能改。
比如:
SELECT * FROM accounts WHERE id = 1 LOCK IN SHARE MODE;
MySQL 8.0 也可以写:
SELECT * FROM accounts WHERE id = 1 FOR SHARE;
这个语句会给读取到的记录加共享锁。
共享锁之间是兼容的。
也就是说:
| 操作 | 是否可以同时进行 |
|---|---|
| 事务 A 读,事务 B 读 | 可以 |
| 事务 A 读,事务 B 改 | 不可以 |
6. 排他锁:X Lock
排他锁又叫 写锁。
含义是:我正在修改这条数据,别人不能读锁定读,也不能改。
常见的语句会加排他锁:
UPDATE accounts SET money = money - 100 WHERE id = 1;
DELETE FROM accounts WHERE id = 1;
SELECT * FROM accounts WHERE id = 1 FOR UPDATE;
排他锁很霸道。
| 当前锁 | 其他事务加共享锁 | 其他事务加排他锁 |
|---|---|---|
| 共享锁 S | 可以 | 不可以 |
| 排他锁 X | 不可以 | 不可以 |
简单记:
- S 锁:大家可以一起读
- X 锁:我要写,别人别碰
二、表锁和行锁
7. 表锁
表锁就是锁住整张表。
例如:
LOCK TABLES accounts WRITE;
表锁粒度大,影响范围广。
如果一张表被写锁锁住,其他事务基本没法正常写这张表。
优点:
- 管理简单
- 开销小
缺点:
- 并发性能差
8. 行锁
行锁就是只锁某几行。
比如:
UPDATE accounts SET money = money - 100 WHERE id = 1;
如果 id 是索引,InnoDB 通常只锁 id = 1 这一行。
优点:
- 并发性能高
- 不同行之间可以同时修改
缺点:
- 管理复杂
- 可能死锁
- 如果没用好索引,可能锁很多行
InnoDB 的核心优势之一就是:支持行级锁。
三、InnoDB 行锁不是锁“行”,而是锁“索引”
这是非常重要的一点。
很多初学者会以为:
UPDATE users SET name = 'A' WHERE id = 10;
就是锁住表里 id = 10 的那一行。
更准确地说:
InnoDB 的行锁是加在索引记录上的。
也就是说,如果你的查询条件命中了索引,InnoDB 可以精确锁住相关索引记录。
如果没有索引,InnoDB 可能扫描很多记录,锁的范围就会扩大。
例如有表:
CREATE TABLE users (
id INT PRIMARY KEY,
name VARCHAR(50),
age INT
);
id 是主键索引。
执行:
UPDATE users SET name = 'Tom' WHERE id = 1;
这通常只锁主键索引上 id = 1 的记录。
但如果 age 没有索引:
UPDATE users SET name = 'Tom' WHERE age = 18;
InnoDB 需要扫描很多行来判断谁的 age = 18,锁的范围可能大得多。
所以一个非常实用的结论是:
想减少锁冲突,首先要让 SQL 正确使用索引。
四、Record Lock:记录锁
9. 什么是记录锁?
Record Lock 就是锁住一条索引记录。
比如表里有:
id: 1, 5, 10
执行:
SELECT * FROM users WHERE id = 5 FOR UPDATE;
如果 id = 5 存在,InnoDB 会给 id = 5 这条索引记录加排他锁。
此时另一个事务执行:
UPDATE users SET name = 'Jerry' WHERE id = 5;
会被阻塞。
但是它可以更新其他行:
UPDATE users SET name = 'Jerry' WHERE id = 10;
一般不会受影响。
五、Gap Lock:间隙锁
10. 什么是间隙?
假设索引里有这些值:
1, 5, 10
那么索引之间有几个“空隙”:
(-∞, 1)
(1, 5)
(5, 10)
(10, +∞)
这些空隙就叫 gap,也就是间隙。
Gap Lock 就是锁住某个范围的“空隙”,防止其他事务往这个范围里插入新数据。
11. 为什么需要间隙锁?
为了防止 幻读。
幻读是这样的:
事务 A:
START TRANSACTION;
SELECT * FROM users WHERE age BETWEEN 10 AND 20;
查到 3 条记录。
这时候事务 B 插入一条:
INSERT INTO users(id, age) VALUES(100, 15);
COMMIT;
事务 A 再查一次:
SELECT * FROM users WHERE age BETWEEN 10 AND 20;
发现变成了 4 条。
好像多出来一条“幻影记录”,这就叫幻读。
为了避免这种情况,InnoDB 在某些隔离级别下会使用 Gap Lock,锁住范围,阻止别人插入满足条件的新记录。
12. 间隙锁的特点
Gap Lock 锁的是“空隙”,不是已有记录。
比如索引值:
1, 5, 10
如果锁住 (5, 10) 这个间隙,那么其他事务不能插入:
INSERT INTO users(id) VALUES(6);
INSERT INTO users(id) VALUES(7);
INSERT INTO users(id) VALUES(9);
但是不一定影响已有的 5 和 10 记录本身。
间隙锁之间通常不互斥。
也就是说,两个事务可以同时持有同一个间隙的 Gap Lock。
这听起来有点奇怪,但它的主要目的不是保护已有数据,而是防止插入。
六、Next-Key Lock:临键锁
13. 什么是 Next-Key Lock?
Next-Key Lock = Record Lock + Gap Lock。
它锁住:
一个索引记录 + 它前面的间隙
例如索引值:
1, 5, 10
Next-Key Lock 可能锁:
(-∞, 1]
(1, 5]
(5, 10]
(10, +∞)
注意括号:
(表示不包含]表示包含
所以 (1, 5] 表示:
- 锁住
1和5之间的间隙 - 也锁住
5这条记录
14. 为什么 Next-Key Lock 很重要?
InnoDB 在默认隔离级别 REPEATABLE READ 下,为了解决幻读,常常使用 Next-Key Lock。
例如:
SELECT * FROM users WHERE id BETWEEN 5 AND 10 FOR UPDATE;
它不仅要锁住已有的 id = 5、id = 10 等记录,还要锁住范围内的间隙,防止别人插入新的 id = 6、id = 7。
这样事务里再次查询时,就不会突然多出新记录。
七、隔离级别和锁的关系
MySQL 事务隔离级别有四种:
| 隔离级别 | 脏读 | 不可重复读 | 幻读 |
|---|---|---|---|
| READ UNCOMMITTED | 可能 | 可能 | 可能 |
| READ COMMITTED | 不可能 | 可能 | 可能 |
| REPEATABLE READ | 不可能 | 不可能 | InnoDB 通常能避免 |
| SERIALIZABLE | 不可能 | 不可能 | 不可能 |
MySQL InnoDB 默认是:
REPEATABLE READ
可以查看:
SELECT @@transaction_isolation;
15. 脏读是什么?
事务 A 修改了数据但还没提交:
UPDATE accounts SET money = 0 WHERE id = 1;
事务 B 读到了 money = 0。
后来事务 A 回滚了。
那事务 B 读到的就是从来没有真正生效的数据。
这叫脏读。
16. 不可重复读是什么?
事务 A 第一次读:
SELECT money FROM accounts WHERE id = 1;
读到 1000。
事务 B 修改并提交:
UPDATE accounts SET money = 900 WHERE id = 1;
COMMIT;
事务 A 第二次读同一行,读到 900。
同一个事务里,同一条记录前后读到不同结果,这叫不可重复读。
17. 幻读是什么?
事务 A 第一次查范围:
SELECT * FROM users WHERE age >= 18;
有 10 条。
事务 B 插入一条 age = 20 的记录并提交。
事务 A 第二次查:
SELECT * FROM users WHERE age >= 18;
有 11 条。
同一个事务里,同一个范围前后查到的“行数”变了,这叫幻读。
八、MVCC:为什么普通 SELECT 不加锁也能安全读?
18. MVCC 是什么?
MVCC 全称是:
Multi-Version Concurrency Control
多版本并发控制
它的核心思想:
数据库给数据保存多个版本,读操作可以读某个历史版本,不一定要等写操作完成。
这就像文档有版本历史:
- 你正在看昨天的版本
- 别人正在编辑今天的版本
- 你们不一定互相阻塞
InnoDB 的普通 SELECT 通常是 快照读,不加行锁。
例如:
SELECT * FROM users WHERE id = 1;
在 REPEATABLE READ 下,事务第一次普通查询时会生成一个一致性视图,后续普通查询继续看这个视图。
所以即使别人提交了修改,你在当前事务里普通查询看到的结果也可能不变。
19. 快照读和当前读
InnoDB 里读分两类。
快照读
普通 SELECT:
SELECT * FROM users WHERE id = 1;
特点:
- 不加锁
- 读历史版本
- 依赖 MVCC
- 并发性能高
当前读
这些语句读取最新数据,并且会加锁:
SELECT * FROM users WHERE id = 1 FOR UPDATE;
SELECT * FROM users WHERE id = 1 FOR SHARE;
UPDATE users SET name = 'Tom' WHERE id = 1;
DELETE FROM users WHERE id = 1;
INSERT INTO users VALUES (...);
特点:
- 读最新版本
- 会加锁
- 可能阻塞别人
- 也可能被别人阻塞
简单记:
| 类型 | 示例 | 是否加锁 | 读什么 |
|---|---|---|---|
| 快照读 | 普通 SELECT | 通常不加锁 | 历史一致版本 |
| 当前读 | SELECT FOR UPDATE / UPDATE / DELETE | 加锁 | 最新数据 |
九、意向锁
20. 意向锁是什么?
意向锁是表级锁,但它不是为了真正锁整张表,而是为了告诉别人:
我接下来要在这张表里的某些行上加锁。
有两种:
| 锁 | 含义 |
|---|---|
| IS | Intention Shared Lock,意向共享锁 |
| IX | Intention Exclusive Lock,意向排他锁 |
比如:
SELECT * FROM users WHERE id = 1 FOR SHARE;
会先在表上加 IS 锁,然后在行上加 S 锁。
UPDATE users SET name = 'Tom' WHERE id = 1;
会先在表上加 IX 锁,然后在行上加 X 锁。
21. 为什么需要意向锁?
假设事务 A 已经锁了表里的某一行:
UPDATE users SET name = 'Tom' WHERE id = 1;
现在事务 B 想锁整张表:
LOCK TABLES users WRITE;
数据库需要判断:
这张表里有没有某些行已经被别人锁住?
如果没有意向锁,它可能要扫描整张表的每一行,看有没有行锁,成本很高。
有了意向锁以后就简单了:
- A 在表上有 IX 锁
- B 想加表级写锁
- 一看 IX 和表级写锁冲突,就知道不能加
所以意向锁是一个“提示牌”。
它提高的是锁冲突判断效率。
十、插入意向锁
22. 插入意向锁是什么?
插入意向锁是 Insert Intention Lock。
当事务要往某个间隙里插入数据时,会先加插入意向锁。
例如索引里有:
1, 10
事务 A 插入 5:
INSERT INTO users(id) VALUES(5);
事务 B 插入 6:
INSERT INTO users(id) VALUES(6);
它们都在 (1, 10) 这个间隙里。
如果没有其他锁阻止,它们通常可以并发插入,因为插入的位置不同。
插入意向锁的作用是:
表示我打算在这个 gap 里插入一条记录。
它和普通 Gap Lock 的关系比较关键:
- 多个插入意向锁之间通常不冲突
- 插入意向锁会被已有的 Gap Lock / Next-Key Lock 阻塞
十一、自增锁
23. 自增锁是什么?
如果表有自增主键:
CREATE TABLE orders (
id INT AUTO_INCREMENT PRIMARY KEY,
name VARCHAR(50)
);
执行:
INSERT INTO orders(name) VALUES('A');
MySQL 会自动生成 id。
为了保证自增值分配正确,InnoDB 有自增锁机制。
早期自增锁粒度比较粗,后来 MySQL 做了优化。
可以通过参数查看:
SHOW VARIABLES LIKE 'innodb_autoinc_lock_mode';
常见值:
| 值 | 名称 | 含义 |
|---|---|---|
| 0 | traditional | 传统模式,锁比较强 |
| 1 | consecutive | 连续模式,默认常见 |
| 2 | interleaved | 交错模式,并发更高,但复制场景要注意 |
你零基础阶段不必深究,只要知道:
自增列插入时,InnoDB 需要机制保证生成的自增 id 不乱。
十二、MDL 元数据锁
24. MDL 是什么?
MDL 全称:
Metadata Lock
元数据锁
它不是 InnoDB 独有,而是 MySQL Server 层的锁。
它保护的是表结构元数据。
比如:
- 表名
- 字段
- 索引
- 表结构
当你执行:
SELECT * FROM users;
MySQL 会加 MDL 读锁。
意思是:
我正在读这张表,别人不要突然把表结构改了。
当你执行:
ALTER TABLE users ADD COLUMN email VARCHAR(100);
MySQL 会加 MDL 写锁。
意思是:
我要改表结构,别人先别用这张表。
25. MDL 常见问题
一个很经典的问题:
事务 A:
START TRANSACTION;
SELECT * FROM users WHERE id = 1;
-- 不提交
事务 B:
ALTER TABLE users ADD COLUMN age INT;
事务 B 可能被阻塞。
因为事务 A 还持有 MDL 读锁。
更麻烦的是,后续其他对 users 的查询也可能排队,被卡住。
所以线上执行 DDL,例如 ALTER TABLE,需要特别小心。
十三、不同 SQL 会加什么锁?
下面是常见情况。
26. 普通 SELECT
SELECT * FROM users WHERE id = 1;
通常:
- 快照读
- 不加行锁
- 使用 MVCC
27. SELECT FOR UPDATE
SELECT * FROM users WHERE id = 1 FOR UPDATE;
通常:
- 当前读
- 加排他锁 X
- 其他事务不能修改这些记录
- 常用于“查出来后马上要改”的场景
例如抢库存:
START TRANSACTION;
SELECT stock FROM products WHERE id = 1 FOR UPDATE;
UPDATE products SET stock = stock - 1 WHERE id = 1;
COMMIT;
28. SELECT FOR SHARE
SELECT * FROM users WHERE id = 1 FOR SHARE;
通常:
- 当前读
- 加共享锁 S
- 其他事务可以读
- 其他事务不能改
29. UPDATE
UPDATE users SET name = 'Tom' WHERE id = 1;
通常:
- 当前读
- 加排他锁 X
- 修改完成后锁不一定立刻释放,要等事务提交或回滚
30. DELETE
DELETE FROM users WHERE id = 1;
通常:
- 当前读
- 加排他锁 X
31. INSERT
INSERT INTO users(id, name) VALUES(1, 'Tom');
通常:
- 会加插入意向锁
- 会对插入的记录加排他锁
- 如果遇到唯一键冲突,还会涉及唯一索引检查相关锁
十四、锁什么时候释放?
这个很重要。
在 InnoDB 事务里,大部分行锁会持有到事务结束。
也就是:
COMMIT;
或者:
ROLLBACK;
才释放。
例如:
事务 A:
START TRANSACTION;
UPDATE users SET name = 'Tom' WHERE id = 1;
-- 此时不提交
事务 B:
UPDATE users SET name = 'Jerry' WHERE id = 1;
事务 B 会等待。
直到事务 A:
COMMIT;
或者:
ROLLBACK;
事务 B 才能继续。
所以,实际开发里要记住:
事务一定要尽快提交,不要开着事务做慢操作。
比如不要这样:
START TRANSACTION;
UPDATE orders SET status = 'paid' WHERE id = 1;
-- 这里调用第三方支付接口,等了 10 秒
COMMIT;
这会导致锁持有时间很长。
更好的方式通常是:把外部慢操作放在事务外,事务里只做数据库必须保证一致性的短操作。
十五、死锁
32. 什么是死锁?
死锁就是两个事务互相等对方释放锁,谁也走不下去。
例如:
事务 A:
START TRANSACTION;
UPDATE accounts SET money = money - 100 WHERE id = 1;
事务 B:
START TRANSACTION;
UPDATE accounts SET money = money - 200 WHERE id = 2;
然后事务 A 继续:
UPDATE accounts SET money = money + 100 WHERE id = 2;
A 等 B 释放 id = 2。
事务 B 继续:
UPDATE accounts SET money = money + 200 WHERE id = 1;
B 等 A 释放 id = 1。
于是:
A 锁着 1,等 2
B 锁着 2,等 1
这就是死锁。
33. InnoDB 如何处理死锁?
InnoDB 会检测死锁。
一旦发现死锁,它会选择一个事务回滚,让另一个事务继续。
被回滚的事务会报错,常见错误:
Deadlock found when trying to get lock; try restarting transaction
应用程序应该捕获这个错误,并在合适场景下重试事务。
34. 如何减少死锁?
常见经验:
- 多个事务访问多行数据时,保持固定顺序。
比如转账时,总是先锁较小的账户 id,再锁较大的账户 id。
-- 先锁 id 小的
SELECT * FROM accounts WHERE id IN (1, 2) ORDER BY id FOR UPDATE;
- 事务尽量短。
不要在事务中做网络请求、文件处理、复杂计算。
- 给查询条件建合适索引。
避免锁范围扩大。
- 避免一次事务更新太多数据。
大批量更新可以分批做。
- 捕获死锁错误并重试。
死锁在高并发系统里不一定能完全避免,重点是降低概率并能恢复。
十六、锁等待和锁超时
35. 锁等待是什么?
事务 B 想更新一行,但事务 A 已经锁住了这行。
事务 B 就会等待。
如果等太久,会超时。
常见错误:
Lock wait timeout exceeded; try restarting transaction
相关参数:
SHOW VARIABLES LIKE 'innodb_lock_wait_timeout';
默认通常是 50 秒。
36. 死锁和锁等待超时的区别
| 项目 | 死锁 | 锁等待超时 |
|---|---|---|
| 原因 | 两个或多个事务循环等待 | 一个事务等另一个事务太久 |
| 数据库处理 | 主动检测并回滚一个事务 | 等到超时时间后报错 |
| 典型错误 | Deadlock found | Lock wait timeout exceeded |
| 是否一定有环 | 有 | 不一定 |
十七、索引对锁范围的影响
这是 InnoDB 锁里最实用、也最容易踩坑的部分。
假设表:
CREATE TABLE users (
id INT PRIMARY KEY,
age INT,
name VARCHAR(50),
INDEX idx_age(age)
);
数据:
id: 1, age: 10
id: 2, age: 20
id: 3, age: 30
37. 主键等值查询
SELECT * FROM users WHERE id = 2 FOR UPDATE;
如果 id = 2 存在,通常只锁这一条主键记录。
锁范围很小。
38. 唯一索引等值查询
如果有:
email VARCHAR(100) UNIQUE
执行:
SELECT * FROM users WHERE email = 'a@test.com' FOR UPDATE;
如果记录存在,通常锁唯一索引上的这一条记录,以及对应主键记录。
因为唯一索引可以确定只有一条。
39. 普通索引等值查询
SELECT * FROM users WHERE age = 20 FOR UPDATE;
age 不是唯一索引。
可能有很多人 age = 20。
InnoDB 不只要锁 age = 20 的记录,还可能锁相关间隙,防止新的 age = 20 插入。
40. 范围查询
SELECT * FROM users WHERE age BETWEEN 10 AND 30 FOR UPDATE;
范围查询更容易触发 Next-Key Lock。
它会锁住范围里的记录和间隙。
所以范围条件在高并发下更容易造成锁等待。
41. 没有索引的查询
UPDATE users SET name = 'Tom' WHERE name = 'Alice';
如果 name 没有索引,InnoDB 可能扫描大量记录。
实际锁住的行可能远超你的预期。
这就是为什么线上慢 SQL 和锁问题经常同时出现。
一句话:
SQL 越精准命中索引,锁越精准;SQL 越模糊,锁越容易扩大。
十八、一个完整例子:抢库存
假设商品表:
CREATE TABLE products (
id INT PRIMARY KEY,
stock INT NOT NULL
);
数据:
INSERT INTO products(id, stock) VALUES(1, 10);
错误写法:
SELECT stock FROM products WHERE id = 1;
-- 应用判断 stock > 0
UPDATE products SET stock = stock - 1 WHERE id = 1;
问题是高并发下,很多人可能同时读到 stock = 10,然后都去扣。
更好的写法之一:
START TRANSACTION;
SELECT stock FROM products WHERE id = 1 FOR UPDATE;
-- 应用判断 stock > 0
UPDATE products SET stock = stock - 1 WHERE id = 1;
COMMIT;
这样 FOR UPDATE 会锁住商品记录。
同一时间只有一个事务能检查并扣减。
更简洁的写法是原子更新:
UPDATE products
SET stock = stock - 1
WHERE id = 1 AND stock > 0;
然后看影响行数:
- 影响 1 行:扣库存成功
- 影响 0 行:库存不足
这个通常更好,因为事务更短,锁持有时间更少。
十九、如何查看锁问题?
MySQL 8.0 可以看这些表。
42. 查看当前事务
SELECT * FROM information_schema.innodb_trx;
43. 查看锁
SELECT * FROM performance_schema.data_locks;
44. 查看锁等待
SELECT * FROM performance_schema.data_lock_waits;
45. 查看最近一次死锁
SHOW ENGINE INNODB STATUS\G
里面有一段:
LATEST DETECTED DEADLOCK
可以看死锁详情。
二十、最核心的记忆版
如果你是零基础,先记这几条就够用了。
46. 第一层:读写锁
共享锁 S:别人可以一起读,但不能写
排他锁 X:别人不能读锁定读,也不能写
47. 第二层:InnoDB 锁索引
InnoDB 行锁锁的是索引记录。
没有合适索引,锁范围可能变大。
这条非常关键。
48. 第三层:三种行锁算法
Record Lock:锁已有索引记录
Gap Lock:锁记录之间的空隙,防止插入
Next-Key Lock:Record Lock + Gap Lock
49. 第四层:普通 SELECT 通常不加锁
普通 SELECT:快照读,靠 MVCC
SELECT ... FOR UPDATE:当前读,加排他锁
UPDATE / DELETE:当前读,加排他锁
50. 第五层:锁一般事务结束才释放
COMMIT 或 ROLLBACK 后释放锁。
事务越长,锁持有越久,冲突越多。
二十一、最后用一句话串起来
InnoDB 的锁体系可以这样理解:
InnoDB 通过 MVCC 让普通读尽量不加锁,通过行级锁保护正在修改的数据,通过 Record Lock、Gap Lock、Next-Key Lock 控制记录和范围,通过意向锁协调表锁和行锁,最终在事务隔离性和并发性能之间取得平衡。
如果只用一个生活类比:
MVCC 像是给读者看历史版本,行锁像是编辑某一段时贴上的“正在修改”标记,间隙锁像是在一段范围内拉了警戒线,防止别人突然插入新内容。