咨询锁(advisory lock),有的地方翻译为顾问锁,作为Postgresql中一种特有的锁,关于对其介绍,仅从咨询锁的描述性定义来看,一开始还真的没明白这个咨询锁是干什么的。
暂时抛开咨询锁的概念,先说数据库中传统的锁机制。
默认情况下的事务性锁,读/写会自动加锁,读/写完成后会自动解锁(加解锁机制在细节上复杂),这是一种隐式的锁机制,Postgresql也不例外。
对于加锁后的并发控制,也就是默认的写不阻塞读,是通过MVCC解决的,这种锁完全不需要认为干预。
相对于隐式锁机制和MVCC并发控制机制,咨询锁可以认为是一种显式锁,需要人为地控制,这类锁需要显式的申请和释放,在使用这类锁的时候,可以自行控制读写的排他性。
什么场景下使用显式锁?
比如想实现写阻塞读,或者读阻塞读的场景,因为默认的隐式锁加上MVCC机制,是做不到的。
实际业务类型需求的场景也很多:一个经典的问题,并发情况下,对唯一键的存性判断,然后决定存在则更新,不存在则插入这种逻辑,就需要咨询锁,默认的MVCC下是做不到的,当然也不是说咨询锁只能做这个事儿。
再举个例子:多线程编程中的线程共享变量,在读写共享变量时需要线程锁做控制(比如python的lock.acquire()),完成之后释放锁,咨询锁就有点这个味道(当然不完全相同),这些都是隐式锁无法完成的。
查看问官方文档的时候还是吓了一跳,Postgresql有这么多类型的咨询锁。
Table 9-73. Advisory Lock Functions
Name | Return Type | Description |
---|---|---|
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 |
其实细看下去,并不复杂,按照“申请/释放,生效范围”,锁类型,参数个数,等待行为,这个咨询锁从几个维度分类之后,还是比较清晰的。
所有的咨询锁函数都是这几个维度的不同组合,只要弄清楚这些锁的不同维度,上面表格中洋洋洒洒的数十个锁函数,加上备注,理解起来还是比较容易的。
所有的咨询锁函数都是这几个维度的不同组合,只要弄清楚这些锁的不同维度,上面表格中洋洋洒洒的数十个锁函数,加上备注,理解起来还是比较容易的。
如下对生效范围,锁类型,申请/释放,参数个数,等待行为逐一解释:
- 1,申请/释放:有申请就有释放,Session级别的锁需要显式释放,随着连接的关闭自动释放;事务级别的锁也需要显式释放,或者会随着事务的结束(提交或者回滚)一并释放
- 2,锁类型:共享锁和排它锁,比如pg_advisory_lock是排它锁,pg_advisory_lock_shared是共享锁
- 3,生效范围:Session级的或者事务级的,很好理解,比如pg_advisory_lock是添加Session级的排它锁,pg_advisory_xact_lock是申请事务级排它锁
- 4,参数个数,这个看概念是有点蒙的,有的锁函数是1个参数,有的是2个参数,一个参数的情况下,锁是库级别的,举个例子就很容易理解了
SessionA dbtest=> select pg_advisory_lock(id),* from t_advisory1 where id = 1; pg_advisory_lock | id ------------------+---- | 1 (1 row) SessionB dbtest=>select pg_advisory_lock(id),* from t_advisory2 where id = 1; --当前Session一直被挂起,或者说阻塞,直到SessionA解锁。
如果想要设置一个同一个表的同一个Id的锁,相信聪明的少侠一定知道该怎么办了,pg_advisory_lock这个函数重载的两个参数的方法,就是在另外一个维度定义锁定信息的。
这里说的两个参数,可以从不同维度定义锁定目标,而不是单单为了表级别的锁定。
- 5,等待行为,对于锁的申请,其结果有两种可能性,1是申请到了,2是没有申请到,对于没有申请到的情况,有两中可选行为,要么一直等下去,要么不等了直接返回表面没申请到
对于上面所说的,SessionB因为无法获取Id上的排它锁,导致挂起的行为,对应用程序表现的不太友好,也容易造成长时间持有连接造成数据库连接的暴增,如何破解?
如果注意上述列表中锁函数的返回值,就会返现,有一部分返回值是void,一部分返回值是boolean,返回boolean的方法就是可以根据锁定目标时,根据返回值来判断是否成功锁定。
对于范围值为boolean的函数,请求发起后都会立即返回,只不过是如果成功申请到了锁,返回T(true),如果没有成功申请到锁,返回F(False)
这样的话,处理起来就比较灵活一点,而不是在申请不到锁的时候,Session处于一直挂起的状态,用流行专业的术语说就是Session一直hang起(一直不怎么敢用hang这个词,感觉都是大神才能用的)
比如pg_try_advisory_xact_lock(key1 int, key2 int)这个锁,就是:非等待模式_申请_一个参数_事务级_排他锁
这样一来,需要什么类型的锁,或者一个是某个函数实现什么类型的锁效果,从这几个维度区分后,就比较清楚了,而不需要逐个尝试其效果。
以上简单总结了Postgresql中咨询锁的概念和用法,咨询锁作为一种显式定义的锁,青脆爽口,轻便灵活,为处理不同逻辑提供了一定的方便性,但也需要使用咨询锁时的潜在的问题。
简单测试一把
简单测试一把
--锁定某个表的某一行 db01=# select pg_try_advisory_lock(cast('t1'::regclass::oid as int),id),id from t1 where id = 1; pg_try_advisory_lock | id ----------------------+---- t | 1 (1 row) --解锁锁定某个表的某一行 db01=# select pg_advisory_unlock(cast('t1'::regclass::oid as int),id),id from t1 where id = 1; pg_advisory_unlock | id --------------------+---- t | 1 (1 row) --直接基于变量的锁定 db01=# select pg_try_advisory_lock(100,1); pg_try_advisory_lock ---------------------- t (1 row) --同一个Session内可以重复锁定 db01=# select pg_try_advisory_lock(100,1); pg_try_advisory_lock ---------------------- t (1 row) --解锁,解锁成功返回t db01=# select pg_advisory_unlock(100,1); pg_advisory_unlock -------------------- t (1 row) --解锁,解锁成功返回t,多次加锁后需要多次解锁,如果脑袋没问题的话,相信没人会在一个Session或者事务里连续对一个Id加锁,虽然postgre支持这么干 db01=# select pg_advisory_unlock(100,1); pg_advisory_unlock -------------------- t (1 row) --如果解锁的时候锁不存在,解锁失败 db01=# select pg_advisory_unlock(100,1); WARNING: you don''t own a lock of type ExclusiveLock pg_advisory_unlock -------------------- f (1 row) db01=# --如果上一个Session的排它锁解锁之前,其他Session尝试加锁,直接返回失败 db01=# select pg_try_advisory_lock(100,1); pg_try_advisory_lock ---------------------- f (1 row) --活动锁的查看 select locktype as lc,relation::regclass as relname,page||','||tuple as ctid,virtualxid ,transactionid as txid,virtualtransaction,pid,mode,granted from pg_locks ;
if you want do something well, understand it well first.