13.3. 明确锁定

PostgreSQL 提供了各种锁定模式来控制对表中数据的并发访问。在 MVCC 无法提供所需行为的情况下,这些模式可用于应用程序控制的锁定。另外,大多数 PostgreSQL 命令会自动获取适当模式的锁,以确保在执行命令时不会以不兼容的方式删除或修改引用的表。 (例如,TRUNCATE无法安全地与同一表上的其他操作并发执行,因此它获得了表上的排他锁来强制执行该操作.)

要检查数据库服务器中当前未完成的锁的列表,请使用pg_locks系统视图。有关监视锁 Management 器子系统状态的更多信息,请参考Chapter 28

13 .3.1. 表级锁

下面的列表显示了可用的锁定模式以及 PostgreSQL 自动使用它们的上下文。您还可以使用命令LOCK明确获取这些锁中的任何一个。请记住,所有这些锁定模式都是表级锁定,即使名称中包含单词“ row”;锁定模式的名称是历史性的。名称在某种程度上反映了每种锁定模式的典型用法-但语义都相同。一种锁定模式与另一种锁定模式之间的唯一 true 区别是,每种锁定模式相互冲突(请参见Table 13.2)。两个事务不能同时在同一张表上持有冲突模式的锁。 (但是,事务永远不会与自身发生冲突.例如,它可能会在同一表上获得ACCESS EXCLUSIVE锁,然后又获得ACCESS SHARE锁.)非冲突锁模式可以由许多事务并发持有。尤其要注意,某些锁定模式是自冲突的(例如,一次不能由一个以上的事务持有ACCESS EXCLUSIVE锁),而其他模式不是自冲突的(例如,ACCESS SHARE锁定可以由多个事务持有) )。

表级锁定模式

  • ACCESS SHARE

    • 仅与ACCESS EXCLUSIVE锁定模式冲突。

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

  • ROW SHARE

    • EXCLUSIVEACCESS EXCLUSIVE锁定模式冲突。

SELECT FOR UPDATESELECT FOR SHARE命令在目标表上获得此模式的锁(除了在已引用但未选择FOR UPDATE/FOR SHARE的任何其他表上的ACCESS SHARE锁之外)。

  • ROW EXCLUSIVE

    • SHARESHARE ROW EXCLUSIVEEXCLUSIVEACCESS EXCLUSIVE锁定模式冲突。

命令UPDATEDELETEINSERT在目标表上获得此锁定模式(除了在任何其他引用表上的ACCESS SHARE锁定之外)。通常,此锁定模式将通过“修改表中的数据”的任何命令获取。

  • SHARE UPDATE EXCLUSIVE

    • SHARE UPDATE EXCLUSIVESHARESHARE ROW EXCLUSIVEEXCLUSIVEACCESS EXCLUSIVE锁定模式冲突。此模式可防止表发生并发模式更改和VACUUM运行。

VACUUM(无FULL),ANALYZECREATE INDEX CONCURRENTLYCREATE STATISTICSALTER TABLE VALIDATE以及其他ALTER TABLE变体(有关详细信息,请参见ALTER TABLE)获取。

  • SHARE

    • ROW EXCLUSIVESHARE UPDATE EXCLUSIVESHARE ROW EXCLUSIVEEXCLUSIVEACCESS EXCLUSIVE锁定模式冲突。此模式可防止表发生并发数据更改。

CREATE INDEX(无CONCURRENTLY)收购。

  • SHARE ROW EXCLUSIVE

    • ROW EXCLUSIVESHARE UPDATE EXCLUSIVESHARESHARE ROW EXCLUSIVEEXCLUSIVEACCESS EXCLUSIVE锁定模式冲突。此模式可防止表发生并发数据更改,并且是自排它的,因此一次只能有一个会话保存该表。

CREATE COLLATIONCREATE TRIGGER和许多形式的ALTER TABLE(请参见ALTER TABLE)获取。

  • EXCLUSIVE

    • ROW SHAREROW EXCLUSIVESHARE UPDATE EXCLUSIVESHARESHARE ROW EXCLUSIVEEXCLUSIVEACCESS EXCLUSIVE锁定模式冲突。此模式仅允许并发ACCESS SHARE锁定,即仅从表中读取可与持有此锁定模式的事务并行进行。

REFRESH MATERIALIZED VIEW CONCURRENTLY收购。

  • ACCESS EXCLUSIVE

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

DROP TABLETRUNCATEREINDEXCLUSTERVACUUM FULLREFRESH MATERIALIZED VIEW(无CONCURRENTLY)命令获取。许多形式的ALTER TABLE也在此级别获得锁。这也是未明确指定模式的LOCK TABLE语句的默认锁定模式。

Tip

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

一旦获取,锁通常会保留到 Transaction 结束。但是,如果在构建保存点后获得了锁,则如果将保存点回滚到该锁,则会立即释放该锁。这符合ROLLBACK取消自保存点以来命令的所有效果的原则。 PL/pgSQL 异常块中获得的锁也是如此:从该块进行的错误转义将释放在其中获取的锁。

表 13.2 锁定模式冲突

请求的锁定模式当前锁定模式
ACCESS SHAREROW SHAREROW EXCLUSIVE独家共享更新SHARE独家行列EXCLUSIVEACCESS EXCLUSIVE
ACCESS SHARE X
ROW SHARE XX
ROW EXCLUSIVE XXXX
独家共享更新 XXXXX
SHARE XX XXX
独家行列 XXXXXX
EXCLUSIVE XXXXXXX
ACCESS EXCLUSIVEXXXXXXXX

13 .3.2. 行级锁

除表级锁外,还有行级锁,下面列出了这些行级锁以及 PostgreSQL 自动使用它们的上下文。有关行级锁冲突的完整表,请参见Table 13.3。请注意,即使在不同的子事务中,事务也可以在同一行上持有冲突的锁。但是除此之外,两个事务永远不能在同一行上持有冲突的锁。行级锁不影响数据查询。它们仅将* writers 和 lockers *阻止到同一行。行级锁在事务结束时或在保存点回滚期间释放,就像表级锁一样。

行锁定模式

  • FOR UPDATE

    • FOR UPDATE导致SELECT语句检索的行被锁定,就像要进行更新一样。这样可以防止它们被其他事务锁定,修改或删除,直到当前事务结束为止。也就是说,尝试这些行中的UPDATEDELETESELECT FOR UPDATESELECT FOR NO KEY UPDATESELECT FOR SHARESELECT FOR KEY SHARE的其他事务将被阻止,直到当前事务结束为止;否则,这些事务将被阻塞。相反,SELECT FOR UPDATE将 await 已在同一行上运行任何这些命令的并发事务,然后将锁定并返回更新的行(如果删除了该行,则不返回任何行)。但是,在REPEATABLE READSERIALIZABLE事务中,如果自事务开始以来要锁定的行已更改,将引发错误。有关更多讨论,请参见Section 13.4

一行上的任何DELETE以及修改某些列上的值的UPDATE都将获得FOR 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 SHARE没有关键更新FOR UPDATE
关键共享 X
FOR SHARE XX
没有关键更新 XXX
FOR UPDATEXXXX

13 .3.3. 页面级锁

除了表和行锁之外,页面级共享/独占锁还用于控制对共享缓冲池中表页面的读/写访问。在获取或更新一行后,立即释放这些锁。应用程序开发人员通常不需要关心页面级锁,但是出于完整性的考虑,在此提及它们。

13.3.4. Deadlocks

使用显式锁定会增加死锁的可能性,其中两个(或更多)事务各自持有对方想要的锁。例如,如果事务 1 获取表 A 上的排他锁,然后尝试获取表 B 上的排他锁,而事务 2 已经对表 B 进行排他锁,并且现在想要对表 A 进行排他锁,那么任何人都无法进行。 PostgreSQL 自动检测死锁情况并通过中止所涉及的事务之一来解决它们,从而允许其他事务完成。 (确切地说哪个 Transaction 将被中止很难预测,因此不应该依赖.)

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

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语句发现它试图更新的行已被锁定,因此它 await 获取该锁的事务完成。现在,事务二正在 await 事务一完成,然后再 continue 执行。现在,事务一执行:

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

事务一试图获取指定行上的行级锁,但不能这样做:事务二已经拥有了这种锁。因此,它 await 事务二完成。因此,事务一在事务二上被阻塞,事务二在事务一上被阻塞:死锁条件。 PostgreSQL 将检测到这种情况并中止其中一个事务。

防止死锁的最佳方法通常是通过确保所有使用数据库的应用程序以一致的 Sequences 获取多个对象上的锁来避免死锁。在上面的示例中,如果两个事务都以相同 Sequences 更新了行,则不会发生死锁。还应该确保在事务中对某个对象获取的第一个锁是该对象所需的最严格的模式。如果事先验证不可行,则可以通过重试由于死锁而中止的事务来即时处理死锁。

只要未检测到死锁情况,寻求表级或行级锁的事务将无限期地 await 冲突的锁被释放。这意味着应用程序长时间保持事务处于打开状态(例如,在 await 用户 Importing 时)是一个坏主意。

13 .3.5. 咨询锁

PostgreSQL 提供了一种创建具有应用程序定义含义的锁的方法。这些被称为* advisory locks *,因为系统不会强制使用它们-取决于应用程序是否正确使用它们。咨询性锁定对于不适用于 MVCC 模型的锁定策略很有用。例如,咨询锁的常见用法是模拟所谓的“平面文件”数据 Management 系统特有的悲观锁策略。虽然存储在表中的标志可以用于相同的目的,但咨询锁更快,可以避免表膨胀,并在会话结束时由服务器自动清除。

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

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

咨询锁和常规锁都存储在共享内存池中,该共享内存池的大小由配置变量max_locks_per_transactionmax_connections定义。必须注意不要耗尽此内存,否则服务器将根本无法授予任何锁定。这对服务器可授予的咨询锁的数量施加了上限,通常为数万至数十万,具体取决于服务器的配置方式。

在某些情况下,使用建议性锁定方法,尤其是在涉及显式排序和LIMIT子句的查询中,由于要评估 SQL 表达式的 Sequences,因此必须小心控制所获得的锁。例如:

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中仍然可见,但这些锁将是悬空的。

Section 9.26.10中描述了提供用于操纵咨询锁的功能。