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 节。
FOR UPDATE
锁模式也会被任何对行的 DELETE
操作获取,以及对某些列的值进行修改的 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
语句发现它尝试更新的行已被锁定,因此它会等待获取锁的事务完成。事务 2 现在等待事务 1 完成才能继续执行。现在,事务 1 执行
UPDATE accounts SET balance = balance - 100.00 WHERE acctnum = 22222;
事务 1 尝试获取指定行的行级锁,但它无法获取:事务 2 已经持有这样的锁。因此,它会等待事务 2 完成。因此,事务 1 被事务 2 阻塞,事务 2 被事务 1 阻塞:死锁情况。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 节中描述。
如果您在文档中发现任何不正确、与您使用特定功能的体验不符或需要进一步澄清的内容,请使用此表单报告文档问题。