2025年9月25日: PostgreSQL 18 发布!
支持的版本: 当前 (18) / 17 / 16 / 15 / 14 / 13
开发版本: 开发版
不支持的版本: 12 / 11 / 10 / 9.6 / 9.5 / 9.4 / 9.3 / 9.2 / 9.1 / 9.0 / 8.4 / 8.3 / 8.2 / 8.1 / 8.0 / 7.4 / 7.3

13.3. 显式锁定 #

PostgreSQL 提供各种锁定模式来控制对表中数据的并发访问。在以下情况下,这些模式可用于应用程序控制的锁定:MVCC不产生期望的行为。此外,大多数 PostgreSQL 命令会自动获取适当模式的锁,以确保在命令执行期间,被引用的表不会被删除或以不兼容的方式修改。(例如,TRUNCATE 不能安全地与其他操作在同一表上并发执行,因此它会获取表上的 ACCESS EXCLUSIVE 锁来强制执行这一点。)

要检查数据库服务器中当前未完成锁的列表,请使用 pg_locks 系统视图。有关监控锁管理器子系统状态的更多信息,请参阅 第 27 章

13.3.1. 表级锁定 #

PostgreSQL 自动使用的可用锁定模式和上下文列表如下。您也可以使用 LOCK 命令显式获取这些锁中的任何一个。请记住,所有这些锁定模式都是表级锁,即使名称中包含“row”一词;锁定模式的名称是历史遗留的。在某种程度上,这些名称反映了每种锁定模式的典型用法 — 但语义都是相同的。唯一真正的区别在于每种锁定模式与其他锁定模式的冲突集(参见 表 13.2)。两个事务不能在同一表上同时持有冲突模式的锁。(但是,一个事务永远不会与自身冲突。例如,它可能获取 ACCESS EXCLUSIVE 锁,然后又在同一个表上获取 ACCESS SHARE 锁。)非冲突锁定模式可以被多个事务并发持有。请特别注意,某些锁定模式是自冲突的(例如,一个 ACCESS EXCLUSIVE 锁一次只能由一个事务持有),而其他模式则不是自冲突的(例如,一个 ACCESS SHARE 锁可以被多个事务持有)。

表级锁定模式

ACCESS SHARE (AccessShareLock)

仅与 ACCESS EXCLUSIVE 锁定模式冲突。

SELECT 命令在引用的表上获取此模式的锁。通常,任何仅 读取 表而不修改它的查询都会获取此锁定模式。

ROW SHARE (RowShareLock)

EXCLUSIVEACCESS EXCLUSIVE 锁定模式冲突。

SELECT 命令在所有指定了 FOR UPDATEFOR NO KEY UPDATEFOR SHAREFOR KEY SHARE 选项的表上获取此模式的锁(此外,在任何没有显式 FOR ... 锁定选项的其他引用表上也会获取 ACCESS SHARE 锁)。

ROW EXCLUSIVE (RowExclusiveLock)

SHARESHARE ROW EXCLUSIVEEXCLUSIVEACCESS EXCLUSIVE 锁定模式冲突。

UPDATEDELETEINSERTMERGE 命令在目标表上获取此锁定模式(此外,在任何其他引用的表上也会获取 ACCESS SHARE 锁)。通常,任何 修改表数据 的命令都会获取此锁定模式。

SHARE UPDATE EXCLUSIVE (ShareUpdateExclusiveLock)

SHARE UPDATE EXCLUSIVESHARESHARE ROW EXCLUSIVEEXCLUSIVEACCESS EXCLUSIVE 锁定模式冲突。此模式保护表免受并发模式更改和 VACUUM 运行的影响。

VACUUM(不带 FULL)、ANALYZECREATE INDEX CONCURRENTLYCREATE STATISTICSCOMMENT ONREINDEX CONCURRENTLY 以及某些 ALTER INDEXALTER TABLE 变体获取(有关详细信息,请参阅这些命令的文档)。

SHARE (ShareLock)

ROW EXCLUSIVESHARE UPDATE EXCLUSIVESHARE ROW EXCLUSIVEEXCLUSIVEACCESS EXCLUSIVE 锁定模式冲突。此模式保护表免受并发数据更改的影响。

CREATE INDEX(不带 CONCURRENTLY)获取。

SHARE ROW EXCLUSIVE (ShareRowExclusiveLock)

ROW EXCLUSIVESHARE UPDATE EXCLUSIVESHARESHARE ROW EXCLUSIVEEXCLUSIVEACCESS EXCLUSIVE 锁定模式冲突。此模式保护表免受并发数据更改的影响,并且是自排他的,一次只有一个会话可以持有它。

CREATE TRIGGER 和某些形式的 ALTER TABLE 获取。

EXCLUSIVE (ExclusiveLock)

ROW SHAREROW EXCLUSIVESHARE UPDATE EXCLUSIVESHARESHARE ROW EXCLUSIVEEXCLUSIVEACCESS EXCLUSIVE 锁定模式冲突。此模式仅允许并发 ACCESS SHARE 锁,即,只有对表的读取操作可以与持有此锁定模式的事务并行执行。

REFRESH MATERIALIZED VIEW CONCURRENTLY 获取。

ACCESS EXCLUSIVE (AccessExclusiveLock)

与所有模式的锁(ACCESS SHAREROW SHAREROW EXCLUSIVESHARE UPDATE EXCLUSIVESHARESHARE ROW EXCLUSIVEEXCLUSIVEACCESS EXCLUSIVE)冲突。此模式保证持有者是唯一以任何方式访问表的事务。

DROP TABLETRUNCATEREINDEXCLUSTERVACUUM FULLREFRESH MATERIALIZED VIEW(不带 CONCURRENTLY)命令获取。许多形式的 ALTER INDEXALTER TABLE 也在此级别获取锁。这也是 LOCK TABLE 语句的默认锁定模式,除非显式指定了模式。

提示

只有 ACCESS EXCLUSIVE 锁会阻止 SELECT(不带 FOR UPDATE/SHARE)语句。

一旦获取,锁通常会一直持有直到事务结束。但是,如果在建立保存点后获取了锁,那么在回滚到保存点时,该锁会立即被释放。这与 ROLLBACK 取消自保存点以来所有命令效果的原则是一致的。对于在 PL/pgSQL 异常块中获取的锁也是如此:从块中逃逸到错误会释放其中获取的锁。

表 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 EXCL.         X X X X
SHARE UPDATE EXCL.       X X X X X
SHARE     X X   X X X
SHARE ROW EXCL.     X X X X X X
EXCL.   X X X X X X X
ACCESS EXCL. X X X X X X X X

13.3.2. 行级锁定 #

除了表级锁之外,还有行级锁,它们在下面列出了 PostgreSQL 自动使用的上下文。有关行级锁冲突的完整表格,请参阅 表 13.3。请注意,一个事务可以在同一行上持有冲突的锁,即使是在不同的子事务中;但除此之外,两个事务永远不能在同一行上持有冲突的锁。行级锁不会影响数据查询;它们只阻止对同一行的写入者和锁定者。行级锁在事务结束时或在保存点回滚期间释放,与表级锁一样。

行级锁定模式

FOR UPDATE

FOR UPDATE 会锁定 SELECT 语句检索到的行,就像更新一样。这可以防止它们被其他事务锁定、修改或删除,直到当前事务结束。也就是说,其他尝试对这些行执行 UPDATEDELETESELECT FOR UPDATESELECT FOR NO KEY UPDATESELECT FOR SHARESELECT FOR KEY SHARE 的事务将被阻塞,直到当前事务结束;反之,SELECT FOR UPDATE 将等待并发事务在同一行上执行了任何这些命令,然后锁定并返回更新后的行(如果行已被删除,则不返回行)。但是,在 REPEATABLE READSERIALIZABLE 事务中,如果要锁定的行自事务开始以来已更改,则会抛出错误。有关更多讨论,请参阅 第 13.4 节

FOR UPDATE 锁定模式也由任何对行的 DELETE 操作获取,并且也由修改了某些列值的 UPDATE 操作获取。目前,对于 UPDATE 的情况,被考虑的列是那些具有可用于外键的唯一索引的列(因此不考虑部分索引和表达式索引),但这将来可能会改变。

FOR NO KEY UPDATE

行为类似于 FOR UPDATE,但获取的锁较弱:此锁不会阻止尝试在同一行上获取锁的 SELECT FOR KEY SHARE 命令。任何不获取 FOR UPDATE 锁的 UPDATE 操作也会获取此锁定模式。

FOR SHARE

行为类似于 FOR NO KEY UPDATE,但它在每行检索上获取共享锁而不是排他锁。共享锁会阻止其他事务在这些行上执行 UPDATEDELETESELECT FOR UPDATESELECT FOR NO KEY UPDATE,但不会阻止它们执行 SELECT FOR SHARESELECT FOR KEY SHARE

FOR KEY SHARE

行为类似于 FOR SHARE,但锁较弱:SELECT FOR UPDATE 会被阻止,但 SELECT FOR NO KEY UPDATE 不会。键共享锁会阻止其他事务执行 DELETE 或任何更改键值的 UPDATE,但不会阻止其他 UPDATE,也不会阻止 SELECT FOR NO KEY UPDATESELECT FOR SHARESELECT FOR KEY SHARE

PostgreSQL 不会在内存中保留有关已修改行的任何信息,因此一次锁定的行数没有限制。但是,锁定行可能会导致磁盘写入,例如,SELECT FOR UPDATE 会修改选定的行以标记它们已被锁定,因此将导致磁盘写入。

表 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

13.3.3. 页级锁定 #

除了表锁和行锁之外,还使用页级共享/排他锁来控制共享缓冲区池中表页的读/写访问。这些锁在获取或更新行后立即释放。应用程序开发人员通常不需要关心页级锁,但在此提及是为了完整性。

13.3.4. 死锁 #

使用显式锁定会增加死锁的可能性,即两个(或多个)事务各自持有对方想要的锁。例如,如果事务 1 获取了表 A 的排他锁,然后尝试获取表 B 的排他锁,而事务 2 已经排他地锁定了表 B,现在想要表 A 的排他锁,那么两者都无法继续。 PostgreSQL 会自动检测死锁情况,并通过中止其中一个事务来解决它们,允许其他事务完成。(具体哪个事务将被中止很难预测,不应依赖于此。)

请注意,死锁也可能由于行级锁而发生(因此,即使不使用显式锁定,也可能发生)。考虑两个并发事务修改表的情况。第一个事务执行

UPDATE accounts SET balance = balance + 100.00 WHERE acctnum = 11111;

这会获取指定账号的行的行级锁。然后,第二个事务执行

UPDATE accounts SET balance = balance + 100.00 WHERE acctnum = 22222;
UPDATE accounts SET balance = balance - 100.00 WHERE acctnum = 11111;

第一个 UPDATE 语句成功获取了指定行的行级锁,因此成功更新了该行。但是,第二个 UPDATE 语句发现它试图更新的行已被锁定,因此它会等待获取锁的事务完成。事务二现在正在等待事务一完成才能继续执行。现在,事务一执行

UPDATE accounts SET balance = balance - 100.00 WHERE acctnum = 22222;

事务一尝试获取指定行的行级锁,但它不能:事务二已经持有该锁。因此,它等待事务二完成。因此,事务一被事务二阻塞,事务二被事务一阻塞:死锁情况。 PostgreSQL 将检测到这种情况并中止其中一个事务。

防止死锁的最佳方法通常是避免它们,确保所有使用数据库的应用程序以一致的顺序获取多个对象的锁。在上面的示例中,如果两个事务都以相同的顺序更新了行,就不会发生死锁。还应该确保在事务中获取的第一个对象锁是该对象将需要的最严格的模式。如果无法提前验证这一点,那么可以通过重试因死锁而中止的事务来即时处理死锁。

只要没有检测到死锁情况,请求表级或行级锁的事务将无限期地等待冲突锁被释放。这意味着应用程序长时间保持事务打开(例如,在等待用户输入时)是一个坏主意。

13.3.5. 推荐锁 #

PostgreSQL 提供了一种创建具有应用程序定义含义的锁的方法。这些被称为推荐锁,因为系统不强制使用它们 — 正确使用它们取决于应用程序。推荐锁对于不适合 MVCC 模型(多版本并发控制)的锁定策略可能很有用。例如,推荐锁的常见用途是模拟所谓的“扁平文件”数据管理系统典型的悲观锁定策略。虽然表中的标志也可以用于此目的,但推荐锁速度更快,避免了表膨胀,并且服务器会在会话结束时自动清理。

PostgreSQL 中,有两种获取推荐锁的方法:会话级别或事务级别。一旦在会话级别获取,推荐锁将一直持有,直到显式释放或会话结束。与标准锁请求不同,会话级别推荐锁请求不遵循事务语义:在后来被回滚的事务中获取的锁在回滚后仍将持有,同样,即使调用事务稍后失败,解锁也会生效。一个进程可以多次获取同一个推荐锁;对于每个已完成的锁请求,在锁实际释放之前,必须有一个对应的解锁请求。另一方面,事务级别的锁请求比常规锁请求更相似:它们会在事务结束时自动释放,并且没有显式的解锁操作。对于推荐锁的短期使用,这种行为通常比会话级别的行为更方便。同一推荐锁标识符的会话级别和事务级别锁请求将以预期的方式相互阻塞。如果一个会话已经持有给定的推荐锁,它进行的附加请求总是会成功,即使其他会话正在等待该锁;此陈述适用于现有锁持有和新请求是在会话级别还是事务级别。

PostgreSQL 中的所有锁一样,可以在 pg_locks 系统视图中找到当前由任何会话持有的推荐锁的完整列表。

推荐锁和常规锁都存储在一个共享内存池中,其大小由配置变量 max_locks_per_transactionmax_connections 定义。必须小心不要耗尽此内存,否则服务器将无法授予任何锁。这给服务器可授予的推荐锁数量设置了一个上限,通常在几万到几十万之间,具体取决于服务器的配置方式。

在某些情况下使用推荐锁定方法,特别是在涉及显式排序和 LIMIT 子句的查询中,必须小心控制由于 SQL 表达式的评估顺序而获得的锁。例如

SELECT pg_advisory_lock(id) FROM foo WHERE id = 12345; -- ok
SELECT pg_advisory_lock(id) FROM foo WHERE id > 12345 LIMIT 100; -- danger!
SELECT pg_advisory_lock(q.id) FROM
(
  SELECT id FROM foo WHERE id > 12345 LIMIT 100
) q; -- ok

在上面的查询中,第二种形式是危险的,因为 LIMIT 不保证在锁定函数执行之前应用。这可能导致获取应用程序未预期的锁,因此无法释放(直到会话结束)。从应用程序的角度来看,这些锁将是悬空的,尽管它们仍然可以在 pg_locks 中看到。

用于操作推荐锁的函数在 第 9.28.10 节 中描述。

提交更正

如果您在文档中看到任何不正确、与您在使用该功能时的实际体验不符或需要进一步澄清的内容,请使用 此表单 来报告文档问题。