Postgresql中的显式锁

张彤 2022年03月23日 1,022次浏览

PostgreSQL 提供了各种锁模式来控制对表中数据的并发访问,在MVCC不能提供所需锁行为的时候,这些锁模型可用于程序控制。大多数 PostgreSQL 命令会自动获取适当模式的锁,例如,TRUNCATE 不能与同一表上的其他操作并发执行,因此它会获得表上的 ACCESS EXCLUSIVE 锁来强制执行该操作。

本文介绍显式锁,其他pg内部的锁,比如自旋锁等不进行介绍

表级锁

  • Table-level Locks

下面的列表显示了可用的锁模式以及 PostgreSQL 自动使用它们的上下文。
您还可以使用 LOCK 命令显式地获取任何这些锁。
请记住,所有这些锁模式都是表级锁,即使名称包含单词“ row”;

ACCESS SHARE

  • SELECT 命令在引用的表上获取此模式的锁。一般来说,任何只读表而不修改表的查询都会获得这种锁模式。

ROW SHARE

  • SELECT FOR UPDATESELECT FOR SHARE 命令在目标表上获得此模式的锁.

ROW EXCLUSIVE

  • 命令 UPDATEDELETEINSERT 在目标表上获得这种锁模式
  • 修改表中数据的任何命令都会获得这种锁模式

SHARE UPDATE EXCLUSIVE

  • VACUUM (不包括 FULL参数), ANALYZE, CREATE INDEX CONCURRENTLY, CREATE STATISTICS, COMMENT ON ,ALTER TABLE VALIDATE 和其他 ALTER TABLE variants
  • 这种模式保护表免受并发模式更改和 VACUUM 运行的影响。

SHARE

  • CREATE INDEX 获得(不包括 CONCURRENTLY参数)
  • 此模式保护表不受并发数据更改的影响。

SHARE ROW EXCLUSIVE

  • CREATE COLLATION, CREATE TRIGGER, 和许多 ALTER TABLE操作获得
  • 此模式保护表不受并发数据更改的影响,并且是自独占的,因此一次只能有一个会话保存该表。

EXCLUSIVE

  • REFRESH MATERIALIZED VIEW CONCURRENTLY获得
  • 只有对表的读操作才能与持有此锁模式的事务并行进行。

ACCESS EXCLUSIVE

  • DROP TABLETRUNCATEREINDEXCLUSTERVACUUM FULLREFRESH MATERIALIZED VIEW (不包含CONCURRENTLY)命令获取

  • 这也是未显式指定模式的 LOCK TABLE 语句的默认锁定模式

  • 这种模式保证持有者是以任何方式访问表的唯一事务,独占该表

  • 只有 ACCESS EXCLUSIVE 锁才会阻塞 SELECT 语句

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

  • 表级锁之间的冲突
    表级锁之间的冲突.png

行级锁

  • Row-level Locks

  • 除了表级锁之外,还有行级锁,下列的行锁由postgresql自动创建

  • 请注意,即使在不同的子事务中,事务也可以在同一行上持有冲突的锁
    但除此之外,两个事务永远不能在同一行上持有冲突的锁。

  • 行级锁不会影响数据查询; 它们只会阻塞同一行的写入器和锁定器。
    行级锁在事务端或保存点回滚期间释放,就像表级锁一样。

FOR UPDATE

  • FOR UPDATE 会导致 SELECT 语句检索到的行被锁定,就像用于更新一样。这可以防止它们被其他事务锁定、修改或删除,直到当前事务结束。
  • FOR UPDATE 锁模式也可以由行上的任何 DELETE 获取,也可以由修改某些列值的 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 UPDATE、 `SELECT FOR SHARE 或 SELECT FOR KEY SHARE。

  • 行级锁冲突
    16480075921.png

页级锁

  • Page-level Locks
    除了表锁和行锁之外,页级共享/排他锁还用于控制对共享缓冲池中表页的读/写访问。
    这些锁在获取或更新行之后立即释放。应用程序开发人员通常不需要关注页级锁,但是这里提到它们是为了完整性。

死锁

  • Deadlocks

  • 使用显式锁定会增加死锁的可能性,其中两个(或更多)事务各持有对方想要的锁。
    例如,如果事务1获得表 a 上的排他锁,然后试图获得表 b 上的排他锁,而事务2已经获得了排他锁定表 b,现在想要表 a 上的排他锁,那么两者都不能继续。

  • 针对死锁的最佳防御通常是通过确保所有使用数据库的应用程序以一致的顺序获取多个对象上的锁来避免死锁。
    如果两个事务都以相同的顺序更新了行,则不会发生死锁。还应该确保在事务中对对象获取的第一个锁是该对象所需的最具限制性的模式。如果提前验证这一点是不可行的,那么可以动态地通过重新尝试由于死锁而中止的事务来处理死锁。

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

咨询锁

  • Advisory Locks

  • PostgreSQL 提供了一种创建具有应用程序定义的含义的锁的方法。这些锁被称为咨询锁

  • 这些锁被称为咨询锁,因为系统不强制使用它们ーー正确使用它们取决于应用程序。

  • 在 PostgreSQL 中,有两种方法可以获得一个咨询锁: 在会话级或者在事务级
    在会话级别获取之后,将持有通知锁,直到显式释放或会话结束为止。
    与标准锁请求不同,会话级别的咨询锁请求不支持事务语义: 在事务期间获得的锁在事务回滚之后仍将持有,同样,即使调用事务稍后失败,解锁也是有效的。
    锁定可以通过其拥有进程多次获取; 对于每个完成的锁定请求,在锁定实际释放之前必须有一个对应的解锁请求。

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