postgresql Advisory Locks

新加入的Advisory Locks似乎是个不错的特性

http://www.postgresql.org/docs/8.2/interactive/explicit-locking.html#ADVISORY-LOCKS
http://www.postgresql.org/docs/8.2/interactive/functions-admin.html#FUNCTIONS-ADVISORY-LOCKS

这里有一篇介绍的blog
http://merlinmoncure.blogspot.com/

个人的一些实验:
pgamdin III 1.6.1
Postgresql 8.2
windows XP
建立一个测试表
CREATE TABLE tests
(
  id serial NOT NULL,
  name character varying(32),
  CONSTRAINT tests_pkey PRIMARY KEY(id)
)
WITHOUT OIDS;

测试数据
insert into tests(name) values ('dafei'),('feifei'),('afei');

[B]实验一:[/B]
分别打开两个SQL查询窗口A,B
在A中输入
select pg_advisory_lock(id),* from tests where id = 1;
执行
执行成功。


然后在B中输入
select pg_advisory_lock(id),* from tests where id = 1;
执行
阻塞中。。。

然后在A中,输入
select pg_advisory_unlock(id),from tests where id = 1;
执行成功,返回 t
这个时候 ,B中语句执行成功,解除了阻塞。

[B]实验二:[/B]
在A中输入
select pg_advisory_lock(id),* from tests where id = 1;
执行两次,
均执行成功。

然后在B中输入
select pg_advisory_lock(id),* from tests where id = 1;
执行
阻塞中。。。
然后在A中,输入
select pg_advisory_unlock(id),from tests where id = 1;
执行成功,返回 t
但是B仍阻塞,
再次执行
select pg_advisory_unlock(id),from tests where id = 1;
执行成功,返回 t
这个时候 ,B中语句执行成功,解除了阻塞。

[B]实验三:[/B]

在A中输入
select pg_advisory_lock(id),* from tests where id = 1;
执行
执行成功。


然后在B中输入
select pg_advisory_unlock(id),from tests where id = 1;
执行成功,返回 f
然后执行
select pg_advisory_lock(id),* from tests where id = 1;
执行 阻塞中。。。

然后在A中,输入
select pg_advisory_unlock(id),from tests where id = 1;
执行成功,返回 t
这个时候 ,B中语句执行成功,解除了阻塞。

[B]实验四:[/B]

在A中输入
select pg_advisory_lock(id),* from tests where id = 1;
执行
执行成功。


然后在B中输入
select pg_advisory_lock(id),from tests where id = 2;
执行
执行成功。

[B]实验五:[/B]

在A中输入
select pg_advisory_lock(id),* from tests where id = 1;
执行
执行成功。
然后再输入
select * from tests where id = 1;
执行成功


然后在B中输入
select * from tests where id = 1;
执行成功

[B]实验六:[/B]
在A中输入
begin;
select pg_advisory_lock(id),* from tests where id = 1;
commit;
执行
执行成功。


然后在B中输入
select pg_advisory_lock(id),* from tests where id = 1;
执行
阻塞中。。。

然后在A中,输入
select pg_advisory_unlock(id),from tests where id = 1;
执行成功,返回 t
这个时候 ,B中语句执行成功,解除了阻塞。

[B]实验七:[/B]
在A中输入
select pg_advisory_lock(id),* from tests where id = 1;
执行
执行成功。

然后在B中输入
select pg_advisory_lock(id),* from tests where id = 1;
执行
阻塞中。。。

关闭A,这时B仍在阻塞中。

停止运行B,重新执行
select pg_advisory_lock(id),* from tests where id = 1;
执行成功。

[B]实验八:[/B]
在A中输入
select pg_advisory_lock(id),* from tests where id = 1;
执行
执行成功。

然后关闭A

然后在B中输入
select pg_advisory_lock(id),* from tests where id = 1;
执行
执行成功。

[B]对比实验一[/B]
使用相近的for update语句
在A中输入
select * from tests where id = 1 for update;
执行
执行成功。

在B中输入
select * from tests where id = 1 for update;
执行
执行成功。

[B]对比实验二[/B]

在A中输入
begin;
select * from tests where id = 1 for update;
执行
执行成功。

在B中输入
select * from tests where id = 1 for update;
执行
阻塞中。。。
然后在A中执行  commit;
执行成功 , 这个时候 ,B中语句执行成功,解除了阻塞。


[B]实验结果分析[/B]
从对比实验来看
for update所使用的锁 Row Share,会在事务结束时释放。
从实验六来看 advisory lock 不会在事务结束时释放(实验6)。
从实验来看,advisory lock 会在用户当前Session中有效,当会话结束时,会释放所有的advisory lock(实验七,实验八)
不过,从实验七来看,似乎如果其他会话仍在请求同一资源的话,并不回感应到锁定该资源的会话退出导致的advisory lock 释放,
必须下次请求时,才会感应到,不知道这个是不是 一个bug
这一点我的测试结果与本文原作者有些不同。假设A、B两个窗口进行测试,如果A先LOCK,然后B再LOCK,这时B会HANG住。这时如果A使用/q正常退出,那么B是会感应到的并且执行成功;但如果我使用CTRL+Z在A窗口强行退出,那么B是感知不到的,仍然阻塞,这时通过pg_locks仍然可以看到原先A窗口相应的进程在持有锁,通过OS命令KILL掉相应的进程,B就感知到了并执行LOCK成功。)

从实验来看advisory lock 不受事务的影响,在同意个会话中 advisory lock  可以多次lock,但是必须与unlock成对使用(实验二)。
advisory lock 不会造成同一个表被锁定(实验四)
advisory lock 不会阻塞一般的查询,包括for update语句。(两个窗口同时执行select ... for update还是会互锁的)(实验五)


个人感觉advisory lock 最大的优势在于与事务无关,或者 跨事务锁定。
对于 pg_advisory_lock_shared 没有进行详尽实验,
在两个会话中可以同时执行
select pg_advisory_lock_shared(id),* from tests;
但是某个会话中 执行 pg_advisory_lock_shared后,其他会话中执行pg_advisory_lock 会阻塞。


##########下面是转的另一篇关于ADVISORY LOCK的文章########

PostgreSQL 9.1 新增了一个事务级的advisory lock,原来只有SESSION级的。
事务级别的advisory lock不能显性的释放。
advisory lock和系统的MVCC不是一个概念,基本上完全不相干。锁的数量由  max_locks_per_transaction and max_connections 决定。
advisory lock可以在pg_locks视图里面看到。
SESSION级的advisory lock一旦获取,需要手工释放或者SESSION 结束后自动释放。SESSION级别的advisory lock还有一个和事务锁不一样的风格。
如:
1. 在一个begin; end;之间获取SESSION级别的advisory lock时,事务被回滚 或者事务FAILED 的话SESSION advisory锁不会被回滚。
digoal=> begin;
BEGIN
digoal=> select pg_advisory_lock(1);
 pg_advisory_lock 
------------------
 
(1 row)

digoal=> rollback;
ROLLBACK
digoal=> select * from pg_locks where objid=1;
 locktype | database | relation | page | tuple | virtualxid | transactionid | classid | objid | objsubid | virtualtransaction |  pid
  |     mode      | granted 
----------+----------+----------+------+-------+------------+---------------+---------+-------+----------+--------------------+-----
--+---------------+---------
 advisory |    16386 |          |      |       |            |               |       0 |     1 |        1 | 2/9186             | 2598
8 | ExclusiveLock | t
(1 row)

2. 在一个begin; end;之间释放SESSION级别的advisory lock时,事务被回滚或者事务FAILED的话SESSION advisory锁仍被释放。
digoal=> select pg_advisory_lock(1);
 pg_advisory_lock 
------------------
 
(1 row)

digoal=> begin;
BEGIN
digoal=> select pg_advisory_unlock(1);
 pg_advisory_unlock 
--------------------
 t
(1 row)

digoal=> rollback;
ROLLBACK
digoal=> select * from pg_locks where objid=1;
 locktype | database | relation | page | tuple | virtualxid | transactionid | classid | objid | objsubid | virtualtransaction | pid 
| mode | granted 
----------+----------+----------+------+-------+------------+---------------+---------+-------+----------+--------------------+-----
+------+---------
(0 rows)

3. 同一个session 级别的advisory lock可以在一个SESSION里面多次获取,但是释放也要多次释放。
如下,获取两次,释放两次。
digoal=> select pg_advisory_lock(1);
 pg_advisory_lock 
------------------
 
(1 row)

digoal=> select pg_advisory_lock(1);
 pg_advisory_lock 
------------------
 
(1 row)

digoal=> select pg_advisory_unlock(1);
 pg_advisory_unlock 
--------------------
 t
(1 row)

digoal=> select * from pg_locks where objid=1;
 locktype | database | relation | page | tuple | virtualxid | transactionid | classid | objid | objsubid | virtualtransaction |  pid
  |     mode      | granted 
----------+----------+----------+------+-------+------------+---------------+---------+-------+----------+--------------------+-----
--+---------------+---------
 advisory |    16386 |          |      |       |            |               |       0 |     1 |        1 | 2/9198             | 2598
8 | ExclusiveLock | t
(1 row)

digoal=> select pg_advisory_unlock(1);
 pg_advisory_unlock 
--------------------
 t
(1 row)

digoal=> select * from pg_locks where objid=1;
 locktype | database | relation | page | tuple | virtualxid | transactionid | classid | objid | objsubid | virtualtransaction | pid 
| mode | granted 
----------+----------+----------+------+-------+------------+---------------+---------+-------+----------+--------------------+-----
+------+---------
(0 rows)

4. session和transaction 级别的lock在同一个SESSION中共用锁空间,所以在TRANSACTION中获取到了锁,在同一个SESSION中也可以获取到。另一个SESSION就获取不到。TRANSACTION ADVISORY LOCK不可以显性释放,会自动在事务结束(包含提交或回滚,正常或不正常)后释放。
如下
SESSION A:
digoal=> begin;
BEGIN
digoal=> select pg_advisory_xact_lock(1);
 pg_advisory_xact_lock 
-----------------------
 
(1 row)
这里获取到事务级的ADVISORY 锁。

digoal=> select pg_advisory_lock(1);
 pg_advisory_lock 
------------------
 
(1 row)
这里获取到SESSION ADVISORY LOCK。

digoal=> end;
COMMIT
这里自动释放了TRANSACTION ADVISORY LOCK。
SESSION B:
digoal=> select pg_advisory_lock(1);
等待中。。。

SESSION A:
digoal=> select * from pg_locks where objid=1;
 locktype | database | relation | page | tuple | virtualxid | transactionid | classid | objid | objsubid | virtualtransaction |  pid
  |     mode      | granted 
----------+----------+----------+------+-------+------------+---------------+---------+-------+----------+--------------------+-----
--+---------------+---------
 advisory |    16386 |          |      |       |            |               |       0 |     1 |        1 | 2/9202             | 2598
8 | ExclusiveLock | t
 advisory |    16386 |          |      |       |            |               |       0 |     1 |        1 | 4/76               | 2645
0 | ExclusiveLock | f
(2 rows)
看到SESSION B在等待这个锁。

digoal=> select pg_advisory_unlock(1);
 pg_advisory_unlock 
--------------------
 t
(1 row)
这里释放了SESSION A 的SESSION ADVISORY LOCK。

digoal=> select * from pg_locks where objid=1;
 locktype | database | relation | page | tuple | virtualxid | transactionid | classid | objid | objsubid | virtualtransaction |  pid
  |     mode      | granted 
----------+----------+----------+------+-------+------------+---------------+---------+-------+----------+--------------------+-----
--+---------------+---------
 advisory |    16386 |          |      |       |            |               |       0 |     1 |        1 | 4/0                | 2645
0 | ExclusiveLock | t
(1 row)
可以看到B已经获得了这个锁。

由于advisory lock和MVCC不相干。所以不存在和MVCC锁的冲突。适合特殊场景的应用,降低锁冲突或者长时间持锁带来的数据库(如VACUUM释放空间,这个我很久前的BLOG有写过)压力。

advisory lock的应用场景举例(应用控制的锁):
比如数据库里面存储了文件和ID的对应关系,应用程序需要长时间得获得一个锁,然后对文件进行修改,再释放锁。
测试数据:
digoal=> create table tbl_file_info (id int primary key,file_path text);
NOTICE:  CREATE TABLE / PRIMARY KEY will create implicit index "tbl_file_info_pkey" for table "tbl_file_info"
CREATE TABLE
digoal=> insert into tbl_file_info values (1,'/home/postgres/advisory_lock_1.txt');
INSERT 0 1
digoal=> insert into tbl_file_info values (2,'/home/postgres/advisory_lock_2.txt');
INSERT 0 1
digoal=> insert into tbl_file_info values (3,'/home/postgres/advisory_lock_3.txt');
INSERT 0 1

SESSION A:
digoal=> select pg_advisory_lock(id),file_path from tbl_file_info where id=1;
 pg_advisory_lock |             file_path              
------------------+------------------------------------
                  | /home/postgres/advisory_lock_1.txt
(1 row)
应用程序对/home/postgres/advisory_lock_1.txt文件进行编辑之后,再释放这个advisory锁。

SESSION B:
当SESSIONA在编辑 /home/postgres/advisory_lock_1.txt这个文件的时候,无法获得这个锁,所以可以确保不会同时编辑这个文件。


如果不使用advisory lock,改用MVCC,来看看如何 : 
仍旧使用以上的测试数据,
SESSION A:
digoal=> begin;
BEGIN
digoal=> select file_path from tbl_file_info where id=1 for update;
             file_path              
------------------------------------
 /home/postgres/advisory_lock_1.txt
(1 row)
应用程序对/home/postgres/advisory_lock_1.txt文件进行编辑之后,再释放这个锁。
digoal=> end;
COMMIT
因此这个SESSION在编辑文件的时候,需要保持这个事务,一方面降低了数据库使用的并发性,另一方面带来了长事务,会有回收不了DEAD TUPLE空间的问题,参考我以前写过的BLOG。
http://blog.163.com/digoal@126/blog/static/163877040201011162912604/
DETAIL:  xxxx dead row versions cannot be removed yet.

SESSION B:
当SESSIONA在编辑  /home/postgres/advisory_lock_1.txt 这个文件的时候,如果需要更改同一个文件,同样,先获得锁然后操作。
digoal=> select file_path from tbl_file_info where id=1 for update;
等待SESSION A释放锁。

【参考】
http://www.postgresql.org/docs/9.1/static/functions-admin.html#FUNCTIONS-ADVISORY-LOCKS
http://www.postgresql.org/docs/9.1/static/explicit-locking.html#ADVISORY-LOCKS

你可能感兴趣的:(PostgreSQL,postgresql,session,insert,database,file,path)