PostgreSQL 行锁解读

    像其它数据库一样,PostgreSQL数据库不仅有表级别的锁,同时也有行级别的锁。行锁对于提高系统的并行度至关重要。那么PostgreSQL是如何实现行锁的呢?

PostgreSQL获取行锁的大致分为以下两个阶段:

1.获取buffer page级别的锁

   这是一个buffer content锁,不是单纯的buffer pin,当然buffer pin的次数肯定会增加。buffer content锁分为share和exclusive两种。”share”是共享锁,

一般的读操作,获取的就是share锁。”exclusive”是排它锁,对记录的修改,增加,删除等需要获取此类锁。

   另外Buffer page又分为两种,分别是local和shared。backend进程独有的buffer是不需要锁的,因为它只被本backend进程独享,是local的。如临时表等。

我们通常需要加锁处理都是针对shared类型的buffer,不同进程之间对buffer page的操作需要通过锁机制来保障。

   在对一个buffer page进行加锁前,需要进行可性性判断。即本条记录是否是“HeapTupleInvisible”。如果是invisible的,是不能加锁,这也是事务隔离的需要。

2.设置行锁标志位

  行锁的实现比较复杂,我们不可能把所有需要上锁的记录都放到内存的lock table中,因为有些情况下,涉及到的记录非常多。为了解决这个问题,通常只在

tuple的header中设置标记为来标识此行记录已经被锁。这两个关键的标记为xmax和infomask。”xmax”放置当前事务的xid,“infomask”放置flag信息。

设置infomask目的主要是为了区别与正常的deleted tuple分区开来,正常情况下xmax是用来标识被删除的记录。这样一来,就不必去维护全局级别的lock table了,

也可以实现任意记录数的行锁操作。另外如果是多个事务同时去上锁一行记录,那么multixact就会被使用。

下面举例说明:

技术文章

上图第一个红色圈内容为一个事务,事务ID号为:15331854,所执行的语句为:

会话1:

postgres=# begin;
BEGIN
postgres=# update t1 set id=1 where id=2;
UPDATE 1

第二个红色圈内容为另一个事务,事务ID号为:15331855,所执行的语句为:

会话2:

postgres=# begin;
BEGIN
postgres=# update t1 set id=2 where id=2;

可以看到,第二个会话hang住了,因为他们更新的是同一个表的同一条记录。

我们再起会话3:

技术文章

看到这条记录的xmax已经标记为第一个事务的xid,即:15331854。这也验证了上面的说法,当update时,PG会去标记每条记录的xmax,设置为当前xid。

另外一个标记位,从这里没有办法看到。需要dump出这条记录的header才能看到。

 

我们再在会话1中跑语句:select xmin,xmax,cmin,cmax ,t.* from t1 t;

技术文章

细心的同学发现,情况不对,跟前面会话3查出的结果完全不同。那么是PG错了吗?

当然不是的,这就是MVCC实现的结果。会话3中看到的那条记录是old tuple,也就是老的记录,因为会话1还没有提交,而会话1看到的是新的记录。

根据read commit隔离级别,本事务中之前update掉的记录,是允许看到的。另外,我们发现xmin的xid已经是本事务的xid了。

 

总结:

PG对于update其实也生成了一个新版本的tuple,在老版本的tuple header中做了两个标记:一个是xmax,另外一个是infomask。

“xmax”放持有此记录lock的xid,infomask存放flag信息。

infomask的flag信息如下:

技术文章

 

另外对于delete操作和update操作,PG做了不同处理。具体主要表现在索引上。

那么行锁与索引有什么关系? 大家知道PG索引是没有MVCC的,那么PG如何处理这些细节呢,下次再详细讲述。

文章来自:http://blog.itpub.net/30088583/viewspace-1699315/
© 2021 jiaocheng.bubufx.com  联系我们
ICP备案:鲁ICP备09046678号-3