PostgreSQL 咨询锁advisory lock使用详解

PostgreSQL从8.2版本就开始支持advisory lock这一功能。那么什么是advisory lock呢?官方文档的解释是:
PostgreSQL提供了一种方法创建由应用定义其含义的锁。这种锁被称为咨询锁,因为系统并不强迫其使用——而是由应用来保证其正确的使用。
advisory lock分为会话级别和事务级别两种(9.1版本加入事务级别)。事务级别的advisory lock不能显性的释放。advisory lock和系统的MVCC不是一个概念,基本上完全不相干。锁的数量由 max_locks_per_transaction and max_connections 决定。

advisory lock相关的函数:
PostgreSQL 咨询锁advisory lock使用详解_第1张图片
advisory lock 不受事务的影响,在同一个会话中 advisory lock 可以多次lock,但是必须与unlock成对使用。

可能看了上面的解释你还是会有些云里雾里的感觉,那么我们就看看advisory lock究竟能用在哪些场景下吧!

–场景1

两个会话,同时对同一个ID值的记录执行删除后插入,有一个会话会出现删除0,插入失败的现象。
建表:

bill@bill=>CREATE TABLE test (id INT PRIMARY KEY);  
CREATE TABLE
bill@bill=>INSERT INTO TEST VALUES (1);  
INSERT 0 1
bill@bill=>INSERT INTO TEST VALUES (2);  
INSERT 0 1

分别在两个会话中执行下列语句:

begin;  
1:DELETE FROM test WHERE id=1;  
2:INSERT INTO test VALUES (1);  
3:COMMIT;  

有点经验的人可能立刻就会发现:在会话二中会因为会话一产生锁等待,直到会话一结束就可以执行自己会话中的语句了。事实真的如此吗?

会话一:

bill@bill=>begin;
BEGIN
bill@bill=>DELETE FROM test WHERE id=1;
DELETE 1
bill@bill=>INSERT INTO test VALUES (1); 
INSERT 0 1
bill@bill=>commit;
COMMIT

会话二:

bill@bill=>begin;
BEGIN
bill@bill=>DELETE FROM test WHERE id=1;
DELETE 0
bill@bill=>INSERT INTO test VALUES (1); 
ERROR:  duplicate key value violates unique constraint "test_pkey"
DETAIL:  Key (id)=(1) already exists.

可以看到,会话二在会话一提交前确实会一直等待,但是等会话一提交完,会话二中delete语句删除记录数为0,insert插入会报错!那么我们该怎么解决这一问题呢?这种情况下我们就可以使用advisory lock。

会话一:

bill@bill=>begin;
BEGIN
bill@bill=>select pg_try_advisory_xact_lock(1);
 pg_try_advisory_xact_lock 
---------------------------
 t
(1 row)

bill@bill=>delete from test where id =1;
DELETE 1
bill@bill=>insert into test values(1);
INSERT 0 1
bill@bill=>end;
COMMIT

会话二:

bill@bill=>begin;
BEGIN
bill@bill=>select pg_try_advisory_xact_lock(1); --会话一不提交则返回f
 pg_try_advisory_xact_lock 
---------------------------
 f
(1 row)

bill@bill=>select pg_try_advisory_xact_lock(1); --会话一提交后返回t
 pg_try_advisory_xact_lock 
---------------------------
 t
(1 row)

bill@bill=>DELETE FROM test WHERE id=1;
DELETE 1
bill@bill=>INSERT INTO test VALUES (1); 
INSERT 0 1
bill@bill=>end;
COMMIT

–场景2:

使用advisory lock消除行锁冲突。
通常在数据库中最小粒度的锁是行锁,当一个事务正在更新某条记录时,另一个事务如果要更新同一条记录(或者申请这一条记录的锁),则必须等待锁释放。
通常持锁的时间需要保持到事务结束,也就是说,如果一个长事务持有了某条记录的锁,其他会话要持有这条记录的锁,可能要等很久。
那么对大表进行全表或者大部分数据的直接更新必然会很慢。

例如:

bill@bill=>create unlogged table t1(id int primary key, info int[]);
CREATE TABLE
bill@bill=>insert into t1 select generate_series(1,10000), (select array_agg(id) from generate_series(1,100000) t(id));
INSERT 0 10000
bill@bill=>\dt+ t1
                   List of relations
 Schema | Name | Type  | Owner |  Size   | Description 
--------+------+-------+-------+---------+-------------
 bill   | t1   | table | bill  | 3961 MB | 
(1 row)

更新:
需要87秒。

bill@bill=>begin;
BEGIN
Time: 0.146 ms
bill@bill=>update t1 set info=array_append(info,1);
UPDATE 10000
Time: 87225.118 ms (01:27.225)
bill@bill=>rollback ;
ROLLBACK
Time: 0.298 ms

使用advisory lock来更新:
使用扫描式的获取advisory lock,保证不会重复获取即可。

create or replace function update() returns void as $$
declare
  v_id int;
begin
  for v_id in select id from t1  -- 扫描式
  loop  
    if pg_try_advisory_xact_lock(v_id) then -- 获取到ID的LOCK才会实施更新,否则继续扫描
      update t1 set info=array_append(info,1) where id=v_id;
    end if;
  end loop;
end;
$$ language plpgsql strict;

使用32个并行度测试:

vi test.sql
select update();

pgbench -M prepared -n -r -f ./test.sql -c 32 -j 32 -t 1

耗时5s。

pg12@oracle-> pgbench -M prepared -n -r -f ./test.sql -c 32 -j 32 -t 1          
transaction type: ./test.sql
scaling factor: 1
query mode: prepared
number of clients: 32
number of threads: 32
number of transactions per client: 1
number of transactions actually processed: 32/32
latency average = 5231.399 ms
tps = 6.116910 (including connections establishing)
tps = 6.135842 (excluding connections establishing)
statement latencies in milliseconds:
      5205.401  select update();

总结:

使用advisory lock需要注意:adlock的id是库级冲突,例如lock(1),在同一个数据库再lock(1)就会冲突。因此,如果在同一个库里面,期望对不同的表里面的数据使用同样的adlock手段,建议不同的表错开ID段,或者使用全局ID。

你可能感兴趣的:(PostgreSQL,sql,数据库,postgresql)