PostgreSQL 表级锁和行级锁的区别?
在 PostgreSQL 中,锁机制是保证数据一致性和并发控制的核心。PostgreSQL 采用了 MVCC(多版本并发控制) 来处理大部分的读写冲突(即“读不阻塞写,写不阻塞读”),但在涉及修改相同数据或更改表结构时,仍然需要使用锁。
PostgreSQL 的锁主要分为表级锁(Table-level Locks)和行级锁(Row-level Locks)。它们的区别主要体现在粒度、并发性能、触发场景以及底层实现机制上。
以下是详细的对比和解析:
1. 核心区别对比表
| 维度 | 表级锁 (Table-level Lock) | 行级锁 (Row-level Lock) |
|---|---|---|
| 锁定粒度 | 整个表 | 表中的某一行或多行 |
| 并发性能 | 较低。容易阻塞其他事务对该表的操作。 | 极高。仅阻塞操作同一行的事务,其他行不受影响。 |
| 触发场景 | DDL操作 (ALTER, DROP), 维护操作 (VACUUM), 显式锁表 (LOCK TABLE),以及DML的意向锁。 |
DML操作 (UPDATE, DELETE),显式行锁 (SELECT ... FOR UPDATE)。 |
| 底层存储 | 存储在内存中的锁表 (Lock Table) 中。 | 存储在数据块(磁盘/内存缓冲区)的行元组头信息中(xmax字段)。 |
| 数量限制 | 受内存限制(max_locks_per_transaction)。 |
无数量限制。锁1行和锁1亿行消耗的内存一样。 |
| 锁升级 | 不适用 | PostgreSQL 绝对不会发生锁升级(不会因为锁了太多行而变成表锁)。 |
2. 表级锁(Table-level Locks)详解
表级锁作用于整个表。当你在表上执行某些操作时,系统会自动获取表级锁。
- 特点:影响面大,主要用于保护表结构不被破坏,或者在进行大规模数据清理时防止干扰。
- 锁的模式:PostgreSQL 共有 8 种表级锁模式。常见的有:
Access Share(访问共享锁):普通的SELECT获取此锁。它只与排他锁冲突。Row Exclusive(行排他锁):INSERT、UPDATE、DELETE获取此锁。它表示“我正在修改表里的行”。Share(共享锁):CREATE INDEX获取此锁。允许别人读,但不允许修改数据。Access Exclusive(访问排他锁):最高级别的锁。DROP TABLE、ALTER TABLE、VACUUM FULL获取此锁。它会阻塞所有对该表的读写操作。
- 注意:即使你只更新表中的一行数据(行级锁),PostgreSQL 也会先在表级别获取一个
Row Exclusive锁,这是为了防止在你更新数据的同时,别人把这个表给删了(DROP)。
3. 行级锁(Row-level Locks)详解
行级锁仅作用于被操作的具体行。
- 特点:并发度极高。事务 A 更新 id=1 的行,事务 B 更新 id=2 的行,两者完全互不干扰。
- 锁的模式:共有 4 种行级锁:
FOR UPDATE:修改行数据(或显式SELECT ... FOR UPDATE)时获取。阻塞其他任何试图修改或锁定该行的操作。FOR NO KEY UPDATE:类似于FOR UPDATE,但不修改主键/唯一键。允许其他事务获取FOR KEY SHARE锁。FOR SHARE:读取数据并希望别人不要修改它。FOR KEY SHARE:只锁定键值,允许别人修改非键值列。
- PG 行锁的特殊实现(重中之重):
与 MySQL (InnoDB) 或 Oracle 将行锁维护在内存结构中不同,PostgreSQL 的行锁是直接写在数据行(Tuple)本身的头部信息里的。 具体来说,是通过修改行头部的xmax事务 ID 字段来实现的。- 优势:由于行锁存在数据本身上,PostgreSQL 可以同时锁定无数行,而不会消耗额外的内存,也永远不会发生锁升级(Lock Escalation,即行锁太多撑爆内存从而升级为表锁导致全表瘫痪,这在某些其他数据库中存在)。
4. 它们是如何协同工作的?
在实际运行中,表级锁和行级锁是配合使用的。
场景:执行 UPDATE users SET name = 'Alice' WHERE id = 1;
- 表级意向:PostgreSQL 首先会在
users表上申请一个 表级Row Exclusive锁。这个锁不会阻止别人读写其他行,但会阻止别人执行DROP TABLE users或ALTER TABLE users。 - 行级锁定:接着,PostgreSQL 找到
id = 1的那一行,在这一行的头部标记xmax为当前事务ID,获取 行级FOR UPDATE锁。这会阻止其他事务同时更新id = 1的这一行。
5. 常见的锁阻塞(死锁与卡顿)场景
- 表级锁阻塞:
- 现象:开发人员在线上执行
ALTER TABLE ADD COLUMN(或者创建普通索引)。 - 结果:获取了表级
Access Exclusive锁,导致该表所有的SELECT,UPDATE,INSERT全部卡死排队,造成线上故障。 - 解决:DDL 操作应在低峰期进行,或者使用并发模式(如
CREATE INDEX CONCURRENTLY)。
- 现象:开发人员在线上执行
- 行级锁阻塞:
- 现象:事务 A 执行了
UPDATE users ... WHERE id = 1且未COMMIT,此时事务 B 也执行UPDATE users ... WHERE id = 1。 - 结果:事务 B 会一直等待,直到事务 A 提交或回滚。
- 解决:确保事务尽可能短,及时
COMMIT或ROLLBACK。
- 现象:事务 A 执行了
总结
- 表级锁是用来管宏观的:保护表结构、协调 DDL 和 DML。存在内存里,粒度大。
- 行级锁是用来管微观的:保护具体数据行的并发修改。存在磁盘数据块的行头里,粒度小,并发高,且在 PostgreSQL 中无数量限制、无锁升级。