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

PostgreSQL 实力解剖锁系列(二) 行级锁

之前我们讨论了表级锁,今天我们要讨论的是行级锁。

行级锁

除了表级锁外, PostgreSQL还有行级锁。其四种形式分别是:

  • FOR UPDATE
  • FOR NO KEY UPDATE
  • FOR SHARE
  • FOR KEY SHARE

实际上,在此之前,我对 insert, update, delete这些不是行级锁有些惊讶。我认为行锁实际上与这些有关,但在 PostgreSQL上却并非如此。我们将研究这四种行级锁定的行为。

FOR UPDATE

它其实是一种悲观的锁定方式,也是开发最喜欢使用的方法。只要修改了一些符合条件的数据,我就会先锁定那些符合条件的数据,不让别人修改,等修改完后,再把释放给其他人。其他人在此期间只能查询数据。

它与update、delete、select fot update、select for no key update、select for share、select for key share互斥。

--会话1
postgres=# begin;
BEGIN
postgres=*# select * from testlock for update;
 id | name 
----+------
  2 | bbb
  5 | sss
  1 | ddd
(3 rows)

--会话2
postgres=# begin;
BEGIN
postgres=*# update testlock set name='aaa' where id=1;

这时会话2将被阻塞,此时可以通过前面的查锁脚本找到阻塞源。

当然这里还可以以其他方式进行查询,即 pgrowlocks扩展,该扩展只能用于行级锁定,如果在表级锁定问题上进行查询,则将卡住无法得到任何结果。

postgres=# \dx
                                            List of installed extensions
        Name        | Version |   Schema   |                              Description                               
--------------------+---------+------------+------------------------------------------------------------------------
 pg_cron            | 1.3     | public     | Job scheduler for PostgreSQL
 pg_stat_statements | 1.8     | public     | track planning and execution statistics of all SQL statements executed
 pgrowlocks         | 1.2     | public     | show row-level locking information
 plpgsql            | 1.0     | pg_catalog | PL/pgSQL procedural language
(4 rows)

postgres=
postgres=# select * from pgrowlocks('testlock');
 locked_row | locker | multi | xids  |     modes      |  pids  
------------+--------+-------+-------+----------------+--------
 (0,2)      |    768 | f     | {768} | {"For Update"} | {2569}
 (0,6)      |    768 | f     | {768} | {"For Update"} | {2569}
 (0,7)      |    768 | f     | {768} | {"For Update"} | {2569}

locked_row显示Tuple ID, modes表示持有者使用的锁定模式,在这里可以清楚地看到是for update。

FOR NO KEY UPDATE

for no key updates类似于for update锁定,但该锁定更弱。对于同一行,它不会阻止 select for key share锁定。任何不获取for update锁的update也会获得这种锁模式。

但是这个锁通常是用于带有主外键的表。让我们看一下它与 for update的区别。分别创建A和B表,设置B的id列是表A的id列的外键。

create table a (id int primary key);
create table b (bid int primary keyid int references a (idon update cascade on delete cascadename varchar);

insert into a(idvalues (1);
insert into a(idvalues (2);

insert into b(bid,id,namevalues (1,1,'xxx');
insert into b(bid,id,namevalues (2,2,'yyy');

「问:现在我使用for update锁定表 A。那么B表还可以插入或更新数据吗?」

--会话1
postgres=# begin;
BEGIN
postgres=*# select * from a for update;
 id 
----
  1
  2
(2 rows)

--会话2
postgres=# insert into b values(3,1,'aaa');

此时会话2会卡住,「无法插入和更新数据」

postgres=# select * from pgrowlocks('a');
 locked_row | locker | multi | xids  |     modes      |  pids  
------------+--------+-------+-------+----------------+--------
 (0,1)      |    834 | f     | {834} | {"For Update"} | {3361}
 (0,2)      |    834 | f     | {834} | {"
For Update"} | {3361}
(2 rows)

postgres=# select * from pgrowlocks('b');
 locked_row | locker | multi | xids | modes | pids 
------------+--------+-------+------+-------+------
(0 rows)

要解决这种问题,您可以使用for no key update,以便 b表能够插入数据。

--会话1
postgres=# begin;
BEGIN
postgres=*# select * from a for no key update;
 id 
----
  1
  2
(2 rows)

--会话2
postgres=# insert into b values(3,1,'aaa');
INSERT 0 1

postgres=# insert into b values(4,2,'aaa');
INSERT 0 1

postgres=# select * from pgrowlocks('a');
 locked_row | locker | multi |   xids    |               modes               |   pids   
------------+--------+-------+-----------+-----------------------------------+----------
 (0,1)      |      5 | t     | {872,873} | {"For No Key Update","Key Share"} | {3527,0}
 (0,2)      |      6 | t     | {872,874} | {"For No Key Update","Key Share"} | {3527,0}

FOR SHARE

这种和for no key updates类似,区别在于所检索的每一行都是共享锁,而非互斥锁。它与update、delete、select fot update、select for no key update互斥,但它可以与select for share、select for key share共享。

这个锁的主要作用就是我们正在读的那一行,我不想有人去改变它。但我不会阻止其他人使用 for share来读取它。

--会话1
postgres=# begin;
BEGIN
postgres=*# select * from a for share;
 id 
----
  1
  2
(2 rows)


--会话2
postgres=# begin;
BEGIN
postgres=*# select * from a for share;
 id 
----
  1
  2
(2 rows)

postgres=# select * from pgrowlocks('a');
 locked_row | locker | multi |   xids    |     modes     |    pids     
------------+--------+-------+-----------+---------------+-------------
 (0,1)      |      4 | t     | {870,871} | {Share,Share} | {3512,3492}
 (0,2)      |      4 | t     | {870,871} | {Share,Share} | {3512,3492}
(2 rows)

FOR KEY SHARE

这与for share相似,但是锁定比较弱。执行select for update会被阻塞。该锁还可以防止其他事务对键值进行update和 delete操作,但是不会阻止非键值列的更新,也不会阻止select for no key update、select for share或者select for key share。该锁主要使用场景还是与主外键约束相关。

介绍了行级锁定模式。通过一个图表,我们可以看到所有锁定互斥现象。

深入理论部分

对表级锁和行级锁进行了研究,得出以下结论::

  • 「表级锁和行级锁在PostgreSQL是各自独立的,但是这两者之间又有着微妙的关系。」

为了搞清楚细节,我搜集了一些资料,资料主要来源于PostgreSQL源代码的readme。

https://github.com/postgres/postgres/blob/322548a8abe225f2cfd6a48e07b99e2711d28ef7/src/backend/storage/lmgr/README

https://github.com/postgres/postgres/blob/c62a0a49f33a0d45a97aa1d3a5bc6ddc83f10d82/src/backend/access/heap/README.tuplock

后记

有兴趣的可以先看看,我已经准备好慢慢啃了。


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

评论