02 - 按粒度分:表锁与行锁
锁有两个最重要的分类维度。这一章讲第一个:粒度——锁住的范围有多大。是锁一整张表,还是只锁其中几行?这直接决定了并发能力。
一句话先记住:锁的范围越小,并发越好,但管理开销越大;范围越大,管理越简单,但并发越差。
2.1 用一个比喻理解粒度
把数据库表想象成一栋楼,每一行是一个房间:
表锁 = 锁住整栋楼的大门
→ 一个人进去,所有人都进不来
→ 简单,但太霸道,并发极差
行锁 = 只锁你要用的那个房间
→ 你用 101 房,别人照样能用 102、103
→ 并发好,但要管很多把小锁,开销大
2.2 表锁(Table Lock)
锁住整张表。粒度最大。
事务A 锁住 users 表
┌──────────────┐
│ users 表 │ 🔒 整张锁住
│ id=1 ... │
│ id=2 ... │ 事务B 想改 id=999 的行?
│ ... │ → 也得等 A 释放整张表的锁
└──────────────┘
- 优点:加锁/解锁开销极小(就一把锁),不会死锁(同类场景下)
- 缺点:并发极差——哪怕两个事务操作的是完全不同的行,也得排队
什么时候用表锁:
- 批量操作整张表(如
ALTER TABLE改表结构,PostgreSQL 会加较强的表级锁) - 需要「锁住整张表做一致性操作」的特殊场景
在 PostgreSQL 中,可以手动加表锁:
LOCK TABLE users IN ACCESS EXCLUSIVE MODE; -- 最强的表锁,几乎啥都不让干
实际业务中很少手动加表锁。日常的 DDL(改表结构)操作会自动加,需要注意它可能阻塞线上读写。
2.3 行锁(Row Lock)
只锁住涉及的那几行。粒度最小,是高并发场景的主力。
事务A 只锁 id=1 这一行
┌──────────────┐
│ users 表 │
│ id=1 ... 🔒 │ ← 只锁这行
│ id=2 ... │ ← 事务B 可以照常改这行
│ id=3 ... │ ← 事务C 可以照常改这行
└──────────────┘
- 优点:并发高——不同的行互不影响,大家可以同时操作
- 缺点:要维护很多把锁,内存和管理开销大;多个行锁交错请求容易死锁(见第 06 章)
在 PostgreSQL 中,UPDATE / DELETE 会自动对涉及的行加行锁;也可以手动加:
BEGIN;
SELECT * FROM users WHERE id = 1 FOR UPDATE; -- 手动锁住 id=1 这一行
-- 这期间,别的事务改 id=1 会被阻塞,但改 id=2 不受影响
UPDATE users SET name = '新名字' WHERE id = 1;
COMMIT; -- 释放锁
2.4 表锁 vs 行锁 对比
| 维度 | 表锁 | 行锁 |
|---|---|---|
| 锁范围 | 整张表 | 涉及的几行 |
| 并发能力 | 差(全表排队) | 好(不同行互不影响) |
| 加锁开销 | 极小(一把锁) | 较大(很多把锁) |
| 死锁风险 | 低 | 较高 |
| 典型场景 | 改表结构、全表操作 | 日常增删改(高并发) |
一句话选择:追求并发就用行锁(数据库默认在增删改时就是行锁),只有极少数需要「锁住全表」的场景才用表锁。
2.5 PostgreSQL 的一个重要特点:读不加行锁
这是 PostgreSQL(以及采用 MVCC 的数据库)和「纯锁数据库」的关键区别,先记住结论,第 05 章展开:
在 PostgreSQL 里:
· 普通的 SELECT(读) → 不加锁!靠 MVCC 读一个「快照」
· UPDATE / DELETE → 对涉及行加行锁
· SELECT ... FOR UPDATE → 手动给读加行锁(读了就锁住,防别人改)
结果:读和写「基本不互相阻塞」,这是 PG 高并发的核心优势。
所以在 PostgreSQL 里,你很少因为「有人在读」而被阻塞。这跟你直觉里的「锁」不太一样——原因就是 MVCC。
本章小结
| 你要记住 | 要点 |
|---|---|
| 粒度含义 | 锁住的范围大小 |
| 表锁 | 锁整张表,简单但并发差,用于改表结构 |
| 行锁 | 只锁涉及的行,并发好但开销大,日常主力 |
| 权衡 | 粒度越小并发越好,但管理开销和死锁风险越大 |
| PG 特点 | 普通读不加锁(靠 MVCC),读写基本不互相阻塞 |
下一章预告:粒度讲完了,还有第二个维度——同样是锁一行,「读锁」和「写锁」能不能共存?这就是共享锁与排他锁。
上一章 ← 01 - 为什么需要锁 | 下一章 → 03 - 按模式分:共享锁与排他锁