PostgreSQL 提供了多种不同级别的锁模式,用于精确控制并发数据访问。在执行绝大多数 SQL 命令时,系统会自动申请相应模式的物理锁,以确保在命令执行期间被引用的数据表不会被并发 DDL 销毁或修改。
可以通过系统视图 pg_locks 实时监控当前服务端正在持有的所有未决锁信息。
使用 LOCK 可以显式为整张表加锁。核心表级锁模式按冲突严苛程度递增如下:
表级锁模式
ACCESS SHARE仅与 ACCESS EXCLUSIVE 冲突。任何纯只读查询(SELECT)都会自动在目标表上获取该锁。
ROW SHARE由带有 FOR UPDATE 或 FOR SHARE 的查询自动获取。
ROW EXCLUSIVE由修改表内数据的命令(UPDATE、DELETE、INSERT 与 MERGE)自动获取。
SHARE UPDATE EXCLUSIVE保护数据表免受并发模式变更及并发 VACUUM 干扰。由常规 VACUUM、ANALYZE 与 CREATE INDEX CONCURRENTLY 获取。
SHARE保护数据表免受并发数据写入修改。由常规 CREATE INDEX 获取。
SHARE ROW EXCLUSIVE自排他锁,由部分表结构定义修改命令自动获取。
EXCLUSIVE阻断并发写入与并发读取,仅允许并发的只读查询。
ACCESS EXCLUSIVE最高等级完全排他锁,与所有其他锁模式完全冲突。由 ALTER TABLE、DROP TABLE、TRUNCATE 等命令自动获取。
Table 13.2. 表锁冲突矩阵
| 请求的锁模式 | 当前已持有的锁模式 | |||||||
|---|---|---|---|---|---|---|---|---|
ACCESS SHARE | ROW SHARE | ROW EXCL. | SHARE UPDATE EXCL. | SHARE | SHARE ROW EXCL. | EXCL. | ACCESS EXCL. | |
ACCESS SHARE | X | |||||||
ROW SHARE | X | X | ||||||
ROW EXCLUSIVE | X | X | X | X | ||||
SHARE UPDATE EXCLUSIVE | X | X | X | X | X | |||
SHARE | X | X | X | X | X | |||
SHARE ROW EXCLUSIVE | X | X | X | X | X | X | ||
EXCLUSIVE | X | X | X | X | X | X | X | |
ACCESS EXCLUSIVE | X | X | X | X | X | X | X | X |
行级锁用于控制并发写操作对具体数据行的竞争,完全不影响只读查询。核心行级锁包括:
FOR UPDATE:独占排他行锁。阻止其他事务修改、删除或锁定这些行。由 DELETE 以及更新关键索引列的 UPDATE 自动获取。
FOR NO KEY UPDATE:弱排他行锁。不修改主外键索引列的普通 UPDATE 自动获取,允许其他事务并发执行 FOR KEY SHARE。
FOR SHARE:共享读行锁。阻止其他事务修改或删除该行,但允许并发共享读取。
FOR KEY SHARE:外键检查行锁。在外键关联检查时自动获取,允许并发更新非关键字段。
Table 13.3. 行锁冲突矩阵
| 请求的锁模式 | 当前已持有的锁模式 | |||
|---|---|---|---|---|
| FOR KEY SHARE | FOR SHARE | FOR NO KEY UPDATE | FOR UPDATE | |
| FOR KEY SHARE | X | |||
| FOR SHARE | X | X | ||
| FOR NO KEY UPDATE | X | X | X | |
| FOR UPDATE | X | X | X | X |
除表级锁和行级锁外,系统在共享缓冲池中还使用页级共享/排他锁来控制对表磁盘页面的读写访问。在抓取或更新元组后,这些锁会立即释放。应用开发者通常无需关注页级锁。
当两个或多个事务彼此持有对方下一步操作所需的物理锁时,系统将陷入彼此无限等待的死锁状态。PostgreSQL 内置了死锁检测后台守护机制,一旦检测到死锁成环,系统会自动牺牲并回滚其中一个参与事务以打破死锁。
预防死锁的最佳工程实践是:确保所有业务应用程序在访问多个对象时,始终严格遵循统一、固定的物理先后顺序申请加锁。
PostgreSQL 提供了具有应用自定义业务语义的全局分布式锁机制,被称为咨询锁(Advisory Locks)。系统不会强制执行其约束,完全由应用程序协同使用。咨询锁支持基于 64 位整数键或两个 32 位整数键加锁。
咨询锁支持两类生命周期模式:会话级(Session-level,直到显式调用释放函数或客户端连接断开时才释放)与事务级(Transaction-level,随当前事务的提交或回滚自动释放)。相关管理函数详见 Section 9.28.10。