Skip to content

PostgreSQL - 锁

PostgreSQL 使用一种名为多版本并发控制(MVCC)的复杂模型来管理并发。与使用读锁的系统不同,MVCC 允许读写操作同时进行而互不阻塞。当一行数据被更新时,PostgreSQL 会创建该行的新版本而不是覆盖旧版本。事务会看到数据库的一致“快照”,确保读取操作是非阻塞的,并且具有高性能。

由于 MVCC,您通常不需要手动锁定表进行读取。PostgreSQL 会自动处理大部分锁定,并且是在行级别进行。例如,对一行的 UPDATE 或 DELETE 操作会在该行上自动放置一个排他锁,并在事务期间一直保持,从而阻止其他事务修改它。

虽然自动锁定足以满足大多数操作的需求,但在某些情况下,您需要更高级别的手动控制锁定(例如,整个表)。这通过 LOCK 命令完成,该命令必须在事务块 (BEGIN...COMMIT) 内部执行。

显式锁定的常见用例包括:

  • 执行复杂的、多阶段的数据更新或维护,在此期间您必须阻止任何并发修改以确保一致性。
  • 防止其他会话获取可能与您的事务长时间运行操作冲突的更强锁。
BEGIN;
LOCK TABLE table_name IN lock_mode;
-- ... your operations here ...
-- ... 您的操作在此处 ...
COMMIT;

PostgreSQL 提供了各种锁模式,从限制性最小到限制性最大。其中几个关键的模式是:

  • ACCESS SHARE(由 SELECT 获取):仅与 ACCESS EXCLUSIVE 冲突。
  • ROW EXCLUSIVE(由 UPDATE、INSERT、DELETE 获取):与 SHARE 和 EXCLUSIVE 模式冲突。
  • SHARE:允许其他会话读取(通过 SELECT),但不允许修改表。
  • ACCESS EXCLUSIVE:限制性最强的模式。与所有其他锁冲突。阻止任何其他事务读取或写入表。DROP TABLE 使用的就是这种模式。

没有 UNLOCK TABLE 命令。事务期间获取的所有锁都会在事务以 COMMIT 或 ROLLBACK 结束时自动释放。

当两个(或更多)事务相互等待对方释放锁时,就会发生死锁。PostgreSQL 的锁管理器可以自动检测死锁。当检测到死锁时,它会通过引发错误来终止其中一个事务,允许另一个事务继续执行。

死锁示例:

-- Session 1:
-- 会话 1:
BEGIN;
UPDATE accounts SET balance = balance - 100 WHERE id = 1;
-- Session 2:
-- 会话 2:
BEGIN;
UPDATE accounts SET balance = balance - 200 WHERE id = 2;
-- Session 1 (now waits for Session 2 to release lock on id=2):
-- 会话 1(现在等待会话 2 释放 id=2 上的锁):
UPDATE accounts SET balance = balance + 100 WHERE id = 2;
-- Session 2 (now waits for Session 1 to release lock on id=1 -> DEADLOCK):
-- 会话 2(现在等待会话 1 释放 id=1 上的锁 -> 死锁):
UPDATE accounts SET balance = balance + 200 WHERE id = 1;
-- ERROR: deadlock detected
-- 错误:检测到死锁

最佳实践: 为避免死锁,请确保所有访问多个资源的应用都以相同、一致的顺序锁定它们。

咨询锁(Advisory locks)是一种协作式锁定机制。PostgreSQL 不会自动强制执行它们;这取决于应用程序逻辑来正确地获取和释放它们。它们对于管理应用程序级别的并发非常有用,例如确保后台作业只有一个实例在同时运行。

示例:确保单个后台工作进程。

-- A background worker process would run this:
-- 一个后台工作进程会运行此命令:
-- The number 12345 is an arbitrary key for our lock.
-- 数字 12345 是我们锁定的任意键。
SELECT pg_try_advisory_xact_lock(12345);
-- If the result is 'true', the lock was acquired.
-- 如果结果是 'true',则表示锁已获取。
-- If 'false', another worker already holds the lock.
-- 如果是 'false',则表示另一个工作进程已持有该锁。
-- The lock is automatically released at the end of the transaction.
-- 锁将在事务结束时自动释放。