PostgreSQL 提供了多种锁模式来控制对表中数据的并发访问。这些模式可用于应用程序控制的锁定,适用于MVCC无法提供所需行为的情况。此外,大多数 PostgreSQL 命令会自动获取适当模式的锁,以确保在命令执行期间不会以不兼容的方式删除或修改所引用的表。(例如,TRUNCATE
无法安全地与同一表上的其他操作同时执行,因此它会获取表的 ACCESS EXCLUSIVE
锁以强制执行此操作。)
要查看数据库服务器中当前存在的锁列表,请使用 pg_locks
系统视图。有关监控锁管理器子系统状态的更多信息,请参阅 第 27 章。
以下列出了可用的锁模式以及 PostgreSQL 在其中自动使用它们的上下文。您还可以使用命令 LOCK 显式地获取任何这些锁。请记住,所有这些锁模式都是表级锁,即使名称包含“行”一词;锁模式的名称是历史性的。在某种程度上,名称反映了每种锁模式的典型用法,但语义都是相同的。一种锁模式与另一种锁模式之间唯一的真正区别是每种模式与之冲突的锁模式集(参见 表 13.2)。两个事务不能在同一时间对同一表持有冲突模式的锁。(但是,事务永远不会与自身冲突。例如,它可能会获取 ACCESS EXCLUSIVE
锁,然后在同一表上获取 ACCESS SHARE
锁。)非冲突锁模式可以由多个事务同时持有。尤其要注意,某些锁模式是自冲突的(例如,ACCESS EXCLUSIVE
锁一次只能由一个事务持有),而其他锁模式则不是自冲突的(例如,ACCESS SHARE
锁可以由多个事务持有)。
表级锁模式
ACCESS SHARE
(AccessShareLock
)仅与 ACCESS EXCLUSIVE
锁模式冲突。
SELECT
命令在所引用的表上获取此模式的锁。一般来说,任何只 读取 表而不修改它的查询都会获取此锁模式。
ROW SHARE
(RowShareLock
)与 EXCLUSIVE
和 ACCESS EXCLUSIVE
锁模式冲突。
SELECT
命令在所有指定了 FOR UPDATE
、FOR NO KEY UPDATE
、FOR SHARE
或 FOR KEY SHARE
选项的表上获取此模式的锁(此外,在任何其他未指定任何显式 FOR ...
锁定选项的表上获取 ACCESS SHARE
锁)。
ROW EXCLUSIVE
(RowExclusiveLock
)与 SHARE
、SHARE ROW EXCLUSIVE
、EXCLUSIVE
和 ACCESS EXCLUSIVE
锁模式冲突。
UPDATE
、DELETE
、INSERT
和 MERGE
命令在目标表上获取此锁模式(此外,在任何其他引用的表上获取 ACCESS SHARE
锁)。一般来说,任何 修改表中数据 的命令都会获取此锁模式。
SHARE UPDATE EXCLUSIVE
(ShareUpdateExclusiveLock
)与 SHARE UPDATE EXCLUSIVE
、SHARE
、SHARE ROW EXCLUSIVE
、EXCLUSIVE
和 ACCESS EXCLUSIVE
锁模式冲突。此模式保护表免受并发模式更改和 VACUUM
运行的影响。
由 VACUUM
(不带 FULL
)、ANALYZE
、CREATE INDEX CONCURRENTLY
、CREATE STATISTICS
、COMMENT ON
、REINDEX CONCURRENTLY
和某些 ALTER INDEX
和 ALTER TABLE
变体获取(有关完整详细信息,请参阅这些命令的文档)。
SHARE
(ShareLock
)与 ROW EXCLUSIVE
、SHARE UPDATE EXCLUSIVE
、SHARE ROW EXCLUSIVE
、EXCLUSIVE
和 ACCESS EXCLUSIVE
锁模式冲突。此模式保护表免受并发数据更改的影响。
由 CREATE INDEX
(不带 CONCURRENTLY
)获取。
SHARE ROW EXCLUSIVE
(ShareRowExclusiveLock
)与 ROW EXCLUSIVE
、SHARE UPDATE EXCLUSIVE
、SHARE
、SHARE ROW EXCLUSIVE
、EXCLUSIVE
和 ACCESS EXCLUSIVE
锁模式冲突。此模式保护表免受并发数据更改的影响,并且是自排他的,因此一次只有一个会话可以持有它。
由 CREATE TRIGGER
和某些形式的 ALTER TABLE
获取。
EXCLUSIVE
(ExclusiveLock
)与 ROW SHARE
、ROW EXCLUSIVE
、SHARE UPDATE EXCLUSIVE
、SHARE
、SHARE ROW EXCLUSIVE
、EXCLUSIVE
和 ACCESS EXCLUSIVE
锁模式冲突。此模式仅允许并发 ACCESS SHARE
锁,即,只有对表的读取可以与持有此锁模式的事务并行进行。
由 REFRESH MATERIALIZED VIEW CONCURRENTLY
获取。
ACCESS EXCLUSIVE
(AccessExclusiveLock
)与所有模式的锁冲突(ACCESS SHARE
、ROW SHARE
、ROW EXCLUSIVE
、SHARE UPDATE EXCLUSIVE
、SHARE
、SHARE ROW EXCLUSIVE
、EXCLUSIVE
和 ACCESS EXCLUSIVE
)。此模式保证持有者是唯一以任何方式访问表的事务。
由 DROP TABLE
、TRUNCATE
、REINDEX
、CLUSTER
、VACUUM FULL
和 REFRESH MATERIALIZED VIEW
(不带 CONCURRENTLY
)命令获取。许多形式的 ALTER INDEX
和 ALTER 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 |
除了表级锁,还有行级锁,如下所列,以及它们在 PostgreSQL 自动使用时的上下文。有关行级锁冲突的完整表格,请参见 表 13.3。请注意,即使在不同的子事务中,事务也可以对同一行持有冲突的锁;但除此之外,两个事务永远不能对同一行持有冲突的锁。行级锁不会影响数据查询;它们只阻止对同一行的 写入者和锁持有者。行级锁在事务结束或在保存点回滚时释放,就像表级锁一样。
行级锁模式
FOR UPDATE
FOR UPDATE
使得由 SELECT
语句检索的行被锁定,就好像要进行更新一样。这将阻止其他事务在当前事务结束之前锁定、修改或删除这些行。也就是说,尝试对这些行执行 UPDATE
、DELETE
、SELECT FOR UPDATE
、SELECT FOR NO KEY UPDATE
、SELECT FOR SHARE
或 SELECT FOR KEY SHARE
的其他事务将被阻塞,直到当前事务结束;反之,SELECT FOR UPDATE
将等待一个在同一行上执行了这些命令的并发事务,然后锁定并返回更新后的行(或没有行,如果行已被删除)。但是,在 REPEATABLE READ
或 SERIALIZABLE
事务中,如果要锁定的行自事务开始以来发生了更改,则会抛出错误。有关更详细的讨论,请参见 第 13.4 节.
对行的任何 DELETE
也会获得 FOR UPDATE
锁定模式,对某些列的值进行修改的 UPDATE
也会获得。目前,用于 UPDATE
情况的列集是那些在其上具有唯一索引并且可以在外键中使用的列(因此不考虑部分索引和表达式索引),但这在将来可能会改变。
FOR NO KEY UPDATE
行为类似于 FOR UPDATE
,只是所获得的锁更弱:此锁不会阻止尝试对同一行获取锁的 SELECT FOR KEY SHARE
命令。任何未获取 FOR UPDATE
锁的 UPDATE
也将获得此锁定模式。
FOR SHARE
行为类似于 FOR NO KEY UPDATE
,只是它对每个检索到的行获取共享锁而不是排他锁。共享锁阻止其他事务对这些行执行 UPDATE
、DELETE
、SELECT FOR UPDATE
或 SELECT FOR NO KEY UPDATE
,但它不会阻止它们执行 SELECT FOR SHARE
或 SELECT FOR KEY SHARE
。
FOR KEY SHARE
行为类似于 FOR SHARE
,只是锁更弱:SELECT FOR UPDATE
被阻止,但 SELECT FOR NO KEY UPDATE
不会被阻止。键共享锁阻止其他事务执行 DELETE
或任何更改键值的 UPDATE
,但不会阻止其他 UPDATE
,也不会阻止 SELECT FOR NO KEY UPDATE
、SELECT FOR SHARE
或 SELECT 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 |
除了表锁和行锁之外,页面级共享/排他锁用于控制对共享缓冲池中表页面的读/写访问。这些锁在获取或更新行后立即释放。应用程序开发人员通常无需担心页面级锁,但出于完整性考虑,这里提到了它们。
使用显式锁定可能会增加出现 死锁 的可能性,在这种情况下,两个(或多个)事务分别持有对方需要的锁。例如,如果事务 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 会检测到这种情况并中止其中一个事务。
防止死锁的最佳方法通常是避免它们,方法是确保所有使用数据库的应用程序都以一致的顺序获取对多个对象的锁。在上面的例子中,如果两个事务以相同的顺序更新了行,就不会出现死锁。还应确保在事务中获取的第一个对象的锁是对该对象最严格的模式。如果无法事先验证这一点,那么可以通过重试因死锁而中止的事务来动态处理死锁。
只要没有检测到死锁情况,寻求表级锁或行级锁的事务就会无限期地等待冲突的锁释放。这意味着应用程序不应该长时间保持事务打开(例如,在等待用户输入时)。
PostgreSQL 提供了一种创建具有应用程序定义意义的锁的方法。这些称为 咨询锁,因为系统不会强制执行它们的用法 - 这取决于应用程序是否正确使用它们。咨询锁对于难以适应 MVCC 模型的锁定策略非常有用。例如,咨询锁的常见用途是模拟所谓的 “平面文件” 数据管理系统的典型悲观锁定策略。虽然存储在表中的标志可以用于相同的目的,但咨询锁更快,避免表膨胀,并且在会话结束时会由服务器自动清理。
在 PostgreSQL 中有两种方法可以获取咨询锁:在会话级别或事务级别。在会话级别获取后,咨询锁将一直保持,直到显式释放或会话结束。与标准锁请求不同,会话级咨询锁请求不会遵守事务语义:在事务期间获取的锁在事务随后回滚后仍然会被持有,同样,即使调用事务随后失败,解锁也是有效的。锁可以被其拥有进程多次获取;对于每个完成的锁请求,在锁实际释放之前,必须有一个相应的解锁请求。另一方面,事务级锁请求的行为更像常规锁请求:它们在事务结束时自动释放,并且没有显式解锁操作。对于短期使用咨询锁,这种行为通常比会话级行为更方便。对同一咨询锁标识符的会话级和事务级锁请求将以预期的方式相互阻塞。如果一个会话已经持有给定的咨询锁,那么它对该锁的额外请求总是会成功,即使其他会话正在等待该锁;此语句对现有锁持有和新请求是会话级还是事务级都没有影响。
像 PostgreSQL 中的所有锁一样,在 pg_locks
系统视图中可以找到任何会话当前持有的所有咨询锁的完整列表。
咨询锁和常规锁都存储在共享内存池中,该池的大小由配置变量 max_locks_per_transaction 和 max_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 节 中描述。
如果您在文档中看到任何不正确的内容,与您对特定功能的体验不符,或者需要进一步澄清,请使用 此表格 报告文档问题。