什么是Postgres咨询锁及其用例
#postgres #database #postgressql #lock

数据库锁是一个强大的功能,可以管理对不同类型的数据库资源的同时访问。每个现代数据库管理系统都提供了自己的锁定机制集,邮政邮政编码也是如此。在本文中,我将描述Postgres中的特定锁类型之一 - 咨询锁。

锁简介

让我们从锁的一般描述开始,存在什么类型的锁。当并发交易同时访问相同的数据库资源时,数据库锁定是一种提供数据一致性的机制。锁可以防止访问资源或取决于操作类型(读,写)。因此,锁定机制对于DBM为其交易提供酸保证至关重要[1]。

数据库锁可以通过几种不同的方式进行分类。首先是获得锁的方式。锁可以是显式隐式。经常说,显式锁是由开发人员获得的,但是说它们是由数据库客户端应用程序获得的。另一方面,隐式锁是由数据库服务器自动获得的,以提供数据完整性。本文致力于明确的锁,但是值得一提的是,隐式锁发生的频率要多得多,有时很高兴不要忘记它。
locks are everywhere
执行 select insert 创建index alter table truncate truncate /em>, drop table 真空和许多其他命令。

对锁进行分类的另一种方法是通过获取的资源类型。可能是:

  • 行级 - 应用表中的单个行
  • 表级 - 适用于整个表
  • 页面级 - 用于控制共享缓冲池中表页的读/写访问
  • 数据库级 - 适用于整个数据库

一些数据库服务器可以根据特定数据库中存在的内部实体提供更多类型。

也因其排他性而有所不同。锁的排他性水平决定了获得的锁定是否允许在同一资源上获得其他锁。总的来说,锁可以是独家共享。当获取独家锁定时,数据库不允许任何其他交易在此资源上获取任何其他锁定,而共享锁可以通过几次不同的交易在同一资源上几次获取。但是,这两个极端之间也存在数十种可能的类型,每个DB系统提供了自己的锁定模式集。

那么,什么是咨询锁?

Postgres提供了一种特殊的锁,该锁完全由客户端应用程序驱动。客户端控制何时设置此锁以及何时发布。这种锁称为咨询 [2]。要获取此类锁定客户端,应选择一个唯一的(单个64位值或两个32位值),然后将其传递到Postgres咨询锁定功能之一[3]。

咨询锁的目的是什么?正如Postgres文档所说,它旨在用于锁定应用程序定义的资源。通常,此类资源在数据库中具有直接的类似物,例如域实体映射到数据库行。但是有时可能会有例外(我们稍后会查看它们),并且应用程序定义的资源在数据库中没有类似物。咨询锁非常适合这种情况。前面提到的关键是这种资源的标识符,Postgres使用了其他并发交易,以获取同一资源上的锁。

让我们返回我们的分类。咨询锁显然是由客户端应用程序启动的,并且没有其他方法来获取它们。当我们谈论它获得的资源类型时,它变得越来越有趣。由于它打算用于自定义资源,因此这些资源可以是任何类型的。它可能是一个表,行或一组表行,即使从几个表中。甚至可能根本没有基础数据库资源,可以使用咨询锁来管理仅存储在内存或其他数据库中的资源的访问。

从排他性的角度来看,咨询锁可以共享或排他性。共享锁(由PG_ADVISORY_LOCK_SHARED 函数发起)与其他共享锁没有冲突,它们仅与独家锁相冲突。同时,独家锁(由 pg_advisory_lock 函数发起)与任何其他锁相冲突,既有独家和共享。

Postgres咨询锁的另一个功能是冲突的行为控制。当已经持有给定标识符的锁定时,它允许不同的行为。它可以等到资源解锁并可用(使用 pg_advisory_lock 函数)或只返回false并继续执行(使用 pg_try_advisory_lock pg_try_advisory_lock_lock_shared ,,,< em> pg_try_advisory_xact_log 函数)。

会话和交易级锁

还有另一个重要的属性,对于咨询锁可能会有所不同。这些锁可以在两个不同的级别上获得: session 交易级别。会话级锁( pg_advisory_lock 函数)不取决于当前的交易,并且要保持直到手动解锁(使用 pg_advisory_unlock 函数)或会话结束时。事务级logs( pg_advisory_xact_log 函数)对那些使用Postgres行锁的人更加熟悉 - 他们活到交易结束,不需要手动解锁。

在这种情况下,会话意味着什么?在Postgres中,会话与数据库连接相同。由于可以在几个过程之间共享连接,因此会话级锁可能存在一个问题。当一个锁定资源的过程在称为解锁之前死亡时,会话不会关闭,因为其他进程使用了​​连接。这意味着将锁定锁,直到连接关闭,这可能需要很长时间。因此,我建议只要有可能避免此类问题。

使用PGBOUNCER是另一个细微差别[4]。大多数相对较高的负载项目都使用Postgres和某种连接池,PGBOUNCER是最受欢迎的。如您所知,PGBOUNCER具有多种连接旋转模式。其中之一是汇总交易[5]。在此模式下,仅在交易期间将服务器连接分配给客户端。当PGBouncer注意到交易结束时,连接将返回到池中。它还使得无法使用会话级咨询锁。

用例

上述功能使开发人员在可能的用例中具有很大的灵活性。我只提到其中一些:

  • 管理并发插入单个表格 - 而不是锁定客户端可以使用更特定标识符创建锁的整个表格 - 它可能是外键,或者是字段的某种组合
  • 单个操作的锁定表,但允许其他操作继续进行。示例:您希望一个操作可以专门使用表,而不允许同一操作在同一表上运行;同时,其他操作应被阻止,并应并行运行。它可能是逐步的表处理或分析过程。在这种情况下,使用咨询锁将有助于防止比赛条件。
  • 存储在单独的表中的行锁组。在这种情况下,您可以单独获取每个记录上的锁,但有时最好将它们视为一组
  • 多线程表处理,例如当您将表用作队列时(通常不建议使用)
  • 分布式锁定(但通常有更好的替代方案[6])

例子

作为一个例子,我将在单个表中以简单的限制系统实现。可以说,我们有一个类似于字段的模型User_ID和Object_ID以及一个允许单个用户在一个小时内只能使用20次的业务规则。伪代码:

transaction do 
    likes_count = get_hour_likes_by_user_id(user_id)
    if likes_count < 20
        create_like(user_id, object_id)
    end
end

由于并发请求,此代码显然将无法完成任务。可能会有一个可能的种族条件,因为在获取喜欢计数之后可以更改喜欢的数量,但在创建类似物之前就可以更改。但这可以通过将咨询锁添加到此交易中很容易解决:

transaction do 
    lock_key = crc32(“user_likes_#{user_id}”)
    db_run(“select pg_advisory_xact_lock(#{lock_key})”)

    likes_count = get_hour_likes_by_user_id(user_id)
    if likes_count < 20
        create_like(user_id, object_id)
    end
end

crc32函数此处用于使用CRC算法[7]。

将字符串键转换为整数。

现在,咨询锁可以确保没有使用同一user_id的新类似实体插入表格,直到交易提交并释放锁。

结论

您可以看到,咨询锁是Postgres数据库为开发人员提供的非常强大且灵活的工具。可以适应很多可能的情况。我将在此处留下一些使用咨询锁的提示:

  • 如果可能的话,请使用事务级锁定而不是会话级
  • 使用CRC(或类似)算法从字符串中生成int键 - 它将有助于避免使用类似键的锁锁
  • 可以从特殊的Postgres表 pg_locks by locktype ='Advisory'
  • 多个锁定的多个锁,因此,如果您多次锁定资源,则应将其解锁相同的次数

链接

  1. https://en.wikipedia.org/wiki/ACID
  2. https://www.postgresql.org/docs/current/explicit-locking.html#ADVISORY-LOCKS
  3. https://www.postgresql.org/docs/current/functions-admin.html#FUNCTIONS-ADVISORY-LOCKS
  4. https://www.pgbouncer.org/
  5. https://www.pgbouncer.org/features.html
  6. https://redis.io/docs/manual/patterns/distributed-locks/
  7. https://en.wikipedia.org/wiki/Cyclic_redundancy_check