行锁、表锁
2025/12/5...大约 5 分钟
一、锁
一、全局锁
全局锁会对整个数据库实例加锁,让数据库处于只读状态。
FLUSH TABLES WITH READ LOCK;二、表级锁
2.1 意向锁(Intention Locks)
协调行锁与表锁的共存,避免表锁检查时需要遍历所有行锁。
| 锁类型 | 说明 | 兼容性 |
|---|---|---|
| 意向共享锁(IS) | 事务准备给数据行加行共享锁 | IS之间兼容 |
| 意向排他锁(IX) | 事务准备给数据行加行排他锁 | IX之间兼容 |
加锁规则
- 加行S锁前 → 先加表IS锁
- 加行X锁前 → 先加表IX锁
2.2 表级S/X锁
LOCK TABLES table_name READ; -- 表级S锁
LOCK TABLES table_name WRITE; -- 表级X锁
UNLOCK TABLES; -- 释放表锁兼容性矩阵
| 当前锁\请求锁 | S锁 | X锁 |
|---|---|---|
| S锁 | ✅ | ❌ |
| X锁 | ❌ | ❌ |
2.3 元数据锁(MDL)
- 自动加锁,无需手动操作
- 防止DDL与DML操作冲突
三、行级锁
3.1 行锁(Record Lock)
锁定具体的索引记录。
示例
-- 对id=1的记录加行X锁
SELECT * FROM users WHERE id = 1 FOR UPDATE;- 使用主键/唯一索引的精确查询
- 记录必须存在
3.2 间隙锁(Gap Lock)
锁定索引记录之间的间隙,防止幻读。
表users的id字段:2, 6, 10, 15
间隙分布:(-∞,2), (2,6), (6,10), (10,15), (15,+∞)
示例
-- 锁定间隙(2,6),防止插入id=3,4,5
SELECT * FROM users WHERE id = 3 FOR UPDATE;特性
- 只在REPEATABLE-READ及以上隔离级别有效
- 间隙锁之间不冲突(不同事务可锁定相同间隙)
3.3 临键锁(Next-Key Lock)
记录锁 + 间隙锁的组合,锁定记录本身及之前的间隙。
范围表示
- (2,6]:表示锁定间隙(2,6) + 记录6
- 前开后闭区间
示例
-- 对id=6加临键锁,锁定范围(2,6]
SELECT * FROM users WHERE id = 6 FOR UPDATE;目的
在REPEATABLE-READ级别下防止幻读的主要机制。
3.4 ☆☆ 加锁规则总结(不同隔离级别如何加锁、锁如何用的)
| 查询条件 | 索引类型 | 隔离级别 | 加锁类型 |
|---|---|---|---|
id = 1 | 主键/唯一索引 | 任意 | 行锁 |
user_id = 1 | 非唯一索引 | RR | 临键锁 |
user_id = 1 | 非唯一索引 | RC | 行锁 |
name = 'John' | 无索引 | RR | 表锁/全表行锁 |
id = 3(不存在) | 主键/唯一索引 | RR | 间隙锁 |
3.4.1 不同隔离级别如何加锁、锁如何用的
不同事务的隔离级别所有的写操作都会加锁,只是加的锁不同。
在MySQL/InnoDB的所有隔离级别中,只要涉及数据修改的操作(INSERT、UPDATE、DELETE),都会加锁。这是保证数据一致性的基础。
但不同隔离级别下,锁的类型、范围和持有时间有重要差异。下面是详细分析:
不同隔离级别下的关键差异
- 读未提交(READ UNCOMMITTED)
- 修改操作:加行级排他锁(X锁)。
- 特点:锁的持有时间最短,但会有脏读问题。其他事务可以读到未提交的数据。
- 示例:
-- 事务A
START TRANSACTION;
UPDATE users SET balance = balance - 100 WHERE id = 1; -- 对id=1加X锁
-- 事务B(READ UNCOMMITTED)
SELECT balance FROM users WHERE id = 1; -- 可以读到事务A未提交的修改(脏读)- 读已提交(READ COMMITTED)
- 修改操作:加行级排他锁(X锁)。
- 关键特点:
- 不加间隙锁(特殊情况除外)。
- 执行UPDATE/DELETE时,只锁住实际修改的行,不锁范围。
- 可能导致幻读,因为其他事务可以在间隙中插入新数据。
- 示例:
-- 事务A
START TRANSACTION;
UPDATE orders SET status = 'shipped' WHERE amount > 1000;
-- 只锁住amount>1000的现有行,不加间隙锁
-- 事务B可以插入amount=2000的新订单(幻读)
INSERT INTO orders (amount, status) VALUES (2000, 'new');- 可重复读(REPEATABLE READ)- MySQL默认
- 修改操作:加行级排他锁(X锁)+ 间隙锁(Gap Lock)/临键锁(Next-Key Lock)。
- 关键特点:
- 这是最重要的区别:会加间隙锁来防止幻读。
- UPDATE/DELETE的WHERE条件涉及的范围会被锁住,阻止其他事务插入。
- 示例:
-- 事务A
START TRANSACTION;
UPDATE orders SET status = 'shipped' WHERE amount > 1000;
-- 不仅锁住amount>1000的现有行,还锁住amount>1000的整个范围(间隙锁)
-- 事务B尝试插入amount=2000的新订单会被阻塞!
INSERT INTO orders (amount, status) VALUES (2000, 'new'); -- 等待事务A提交- 串行化(SERIALIZABLE)
- 修改操作:与可重复读完全相同的锁机制(行级排他锁+间隙锁)。
- 关键特点:
- 修改操作的锁行为与RR一致。
- 真正的区别在SELECT上:普通SELECT会自动变成
SELECT ... FOR SHARE(加共享锁)()。
- 示例:
-- 事务A
START TRANSACTION;
UPDATE orders SET status = 'shipped' WHERE amount > 1000;
-- 锁机制与RR完全相同
-- 事务B的普通SELECT也会被阻塞(如果访问相同数据)
SELECT * FROM orders WHERE amount > 1000; -- 会被阻塞总结表格
| 隔离级别 | 修改操作加的锁 | 是否加间隙锁 | 主要目的 |
|---|---|---|---|
| READ UNCOMMITTED | 行级X锁 | 否 | 最低一致性,性能最高 |
| READ COMMITTED | 行级X锁 | 否(特殊情况除外) | 避免脏读,允许不可重复读和幻读 |
| REPEATABLE READ | 行级X锁 + 间隙锁/临键锁 | 是 | 避免脏读、不可重复读,部分防止幻读 |
| SERIALIZABLE | 行级X锁 + 间隙锁/临键锁 | 是 | 完全序列化,避免所有并发问题 |
3.5 手动加锁语法
-- 加行级S锁
SELECT * FROM table_name WHERE ... FOR SHARE;
-- 加行级X锁
SELECT * FROM table_name WHERE ... FOR UPDATE;