暂无图片
暂无图片
暂无图片
暂无图片
暂无图片

PostgreSQL 实力解剖锁系列(三) advisory lock

之前已经学习过表级锁和行级锁,但PostgreSQL还有一种锁叫咨询锁(advisory lock)。

咨询锁和使用场景

PostgreSQL提供了应用程序创建锁的方法,以控制并发访问表中的数据。咨询锁的一般用途,我在网上查过,有这么几个场景可以考虑。

  • 在微服务架构中,应用程序在进行 API调用时需要与多个服务关联交易。
  • 后台程序的并发控制。举例来说,我们希望计算数据并向一些领导发送report,但我们必须保证后台开发人员不会同时开始计算。
  • 多结点任务调度器可以使用咨询锁来协调任务分配给工作程序,或者是指定到不同的PostgreSQL分片中。
  • 秒杀场景,这个案例主要来自于德哥。与 for update no wait相比,在秒杀场景中咨询锁的效率更高。

咨询锁的类型

咨询锁主要分为两类,一类是专用咨询锁,另一类是共享咨询锁。下面的图片来自:https://vladmihalcea.com/how-do-postgresql-advisory-locks-work/

「专用咨询锁」:如图所示,目前有两个用户 Alice和 Bob, Alice申请了专用咨询锁,当 Bob想要使用共享锁获得它时,就会被阻塞。在Alice释放之前, Bob无法获取共享锁。这就是说,专用咨询锁是独占的,在独占期间任何人都无法访问,那怕是读也不行。因此我们可以通过咨询锁实现读的锁定。

「共享咨询锁」,如图所示,目前有3个用户 Alice、 Bob和 Carol,Alice最先申请了一个共享咨询锁, Carol可以继续申请相同的共享咨询锁。如果 Bob现在想申请一个专用的咨询锁,那么就需要等待 Alice和 Bob将该共享咨询锁释放,否则它会一直阻塞。

咨询锁的范围

咨询锁有两个范围,会话级和事务级。会话级锁不与任何数据库事务绑定,一旦获得锁,应用程序开发人员需要明确地释放它。一个事务级的咨询锁绑定到当前正在执行的事务上,因此这些锁定会在事务结束时被释放,无论是提交还是回滚。

官方文档的关于咨询锁的函数非常多,如下所示。

「Table 9-73. Advisory Lock Functions」

NameReturn TypeDescription
pg_advisory_lock(key bigint)
void
Obtain exclusive session level advisory lock
pg_advisory_lock(key1 int, key2 int)
void
Obtain exclusive session level advisory lock
pg_advisory_lock_shared(key bigint)
void
Obtain shared session level advisory lock
pg_advisory_lock_shared(key1 int, key2 int)
void
Obtain shared session level advisory lock
pg_advisory_unlock(key bigint)
boolean
Release an exclusive session level advisory lock
pg_advisory_unlock(key1 int, key2 int)
boolean
Release an exclusive session level advisory lock
pg_advisory_unlock_all()
void
Release all session level advisory locks held by the current session
pg_advisory_unlock_shared(key bigint)
boolean
Release a shared session level advisory lock
pg_advisory_unlock_shared(key1 int, key2 int)
boolean
Release a shared session level advisory lock
pg_advisory_xact_lock(key bigint)
void
Obtain exclusive transaction level advisory lock
pg_advisory_xact_lock(key1 int, key2 int)
void
Obtain exclusive transaction level advisory lock
pg_advisory_xact_lock_shared(key bigint)
void
Obtain shared transaction level advisory lock
pg_advisory_xact_lock_shared(key1 int, key2 int)
void
Obtain shared transaction level advisory lock
pg_try_advisory_lock(key bigint)
boolean
Obtain exclusive session level advisory lock if available
pg_try_advisory_lock(key1 int, key2 int)
boolean
Obtain exclusive session level advisory lock if available
pg_try_advisory_lock_shared(key bigint)
boolean
Obtain shared session level advisory lock if available
pg_try_advisory_lock_shared(key1 int, key2 int)
boolean
Obtain shared session level advisory lock if available
pg_try_advisory_xact_lock(key bigint)
boolean
Obtain exclusive transaction level advisory lock if available
pg_try_advisory_xact_lock(key1 int, key2 int)
boolean
Obtain exclusive transaction level advisory lock if available
pg_try_advisory_xact_lock_shared(key bigint)
boolean
Obtain shared transaction level advisory lock if available
pg_try_advisory_xact_lock_shared(key1 int, key2 int)
boolean
Obtain shared transaction level advisory lock if available

实际上仔细看,锁定模式主要有三种, Acquire、Release和 Try。获得锁,释放锁,尝试锁定。每个函数有两种形式,一种是单参数版本,另一种是双参数版本。单参数值为bigint类型,多参数值为两个 int类型。

咨询锁示例

「那么问题就来了,咨询锁是否锁定了数字呢??」

是啊,它确实锁定了一个数字。让我们看一个案例。

--会话1
postgres=# BEGIN; SELECT pg_try_advisory_xact_lock(1);
BEGIN
 pg_try_advisory_xact_lock 
---------------------------
 t
(1 row)

Session 1执行函数,试图获得锁定,发现可以锁定,返回 true,并锁定该数字。现在,您可以在pg_locks视图中看到相关锁。

postgres=# SELECT clock_timestamp(),pg_locks.locktype,pg_locks.relation,pg_locks.pid, pg_locks.mode, pg_locks.granted from pg_locks where locktype='advisory';
        clock_timestamp        | locktype | relation | pid  |     mode      | granted 
-------------------------------+----------+----------+------+---------------+---------
 2021-01-26 14:47:22.251143+00 | advisory |          | 2706 | ExclusiveLock | t

这时Session 2也想尝试锁定该数字,发现锁定不了,立即返回 flase。实际上,这一作用与我们的 for update no wait相似,只是它的效率高很多。所以适合当作秒杀来替换for update no wait操作。

postgres=# BEGIN; SELECT pg_try_advisory_xact_lock(1);
BEGIN
 pg_try_advisory_xact_lock 
---------------------------
 f
(1 row)

假如Session 2要强行锁定,就会一直卡住。

postgres=# BEGIN; SELECT pg_advisory_xact_lock(1);
BEGIN

此时,可以在pg_locks视图时看到两个ExclusiveLock锁定的记录,其中一个已获得锁定,而另一个正在等待。

postgres=# SELECT clock_timestamp(),pg_locks.locktype,pg_locks.relation,pg_locks.pid, pg_locks.mode, pg_locks.granted from pg_locks where locktype='advisory';
        clock_timestamp        | locktype | relation | pid  |     mode      | granted 
-------------------------------+----------+----------+------+---------------+---------
 2021-01-26 14:53:59.739354+00 | advisory |          | 2708 | ExclusiveLock | f
 2021-01-26 14:53:59.739359+00 | advisory |          | 2712 | ExclusiveLock | t

后记

其实咨询锁的用处很大,但一般开发人员不知道如何使用它,所以还是需要我们 DBA进行大力宣传,至少我觉得可以用把for update no wait替换掉。

参考链接

1.https://www.postgresql.org/docs/9.5/functions-admin.html#FUNCTIONS-ADVISORY-LOCKS-TABLE

2.https://vladmihalcea.com/how-do-postgresql-advisory-locks-work/


文章转载自励志成为PostgreSQL大神,如果涉嫌侵权,请发送邮件至:contact@modb.pro进行举报,并提供相关证据,一经查实,墨天轮将立刻删除相关内容。

评论