PostgreSQL clog详解

PostgreSQL由于其多版本特性,因此需要提交日志clog来记录事务的状态,从而判断其可见性。clog分配于共享内存中,并作用于事务处理过程的全过程。

在pg中定义了4种事务状态,分别是:IN_PROGRESS、COMMITED、ABORTED和SUB_COMMITED。例如事务正在运行中,那么它的状态就是IN_PROGRESS。SUB_COMMITED位子事务的状态,这里我们暂且不讨论。

pg中就是通过clog来存储事务的xid和事务的状态。正因为如此,我们会发现在pg中如果想要取消一个执行了很长时间的事务,基本上是瞬间完成的,而不是像Oracle中一样需要等到undo表空间中内容回滚完,因为pg中只需要将事务的状态由IN_PROGRESS修改为ABORTED即可。

但这样同时也会导致一个问题,如果执行了上述的操作,那么表中必然会存在大量ABORTED状态的数据,当我们后面对该表进行查询的时候,还会去对这些数据进行扫描判断其clog的事务状态,性能必然会很低,那pg中是如何解决这类问题的呢?

这里我们可以从其源码中得到答案:src/backend/access/transam/clog.c
在这里插入图片描述
从这里我们可以看到,pg中通过2个bit位来标识4种事务状态(0、1、2、3),那么1个字节便可以存储4个事务,对于1个8k的page则可以存储8k * 4 = 32k个事务。而当我们判断某个事务的状态时则直接使用 (xid) % (TransactionId) CLOG_XACTS_PER_BYTE 去计算,典型的将文件当作hash表去使用。

而对于pg的32位xid而言,按照xid最大20亿来估算,clog最大也就:20亿/4/1024/1024 = 476MB,竟然还不到500MB,那么查询效率必然不会太低。

在pg中,事务id并不是在事务开始时就会被分配,而是当事务进行了数据修改之类的操作时才会分配xid,而当事务提交或回滚时,其事务状态便会被写入clog中。

正如我们所知,pg中update一行数据时,实质是对旧的数据打上删除的标记,然后再其旁边再插入一行新的数据,同时为了保证数据的可见性,必然也会对其记录一个事务的标记。

例如当我们在事务中写入数据时,可以看到这个事务的状态是IN_PROGRESS,而当我们提交或者回滚该事务时,如果再去修改这个状态必然对性能会产生一定的影响,而且这个操作也是多余的。为什么呢?正如我们前面所说,pg中判断事务的可见性时通过xid去clog中查找事务的状态的,既然查找该数据的时候会去判断并且将改行数据的状态进行修改,那么我们为什么还要在事务提交时进行修改呢?

正因为如此,pg中是这么做的:前一个事务所有修改的数据,它没有在提交或者回滚的当时改掉所有的修改标记,而是等到下次查询时进行修改。

既然我们修改了行的事务状态,那么该行的数据块的校验和就变了,那是不是应该要产生wal日志呢?即使没有产生数据的变化,不然像流复制这种通过wal日志同步的主备环境中,主库的数据块变化了如果不产生wal日志那备库的数据块不是没变吗?那么怎么保证数据可见性的一致呢?

但事实上并不是这样,只有当设置了checksum时才会在设置标记位的时候去写WAL日志。而且也并不是每次设置标记位的时候都去写,而是前一次checkpoint之后,数据块第一次被修改时才会写整个数据块到WAL日志中。

可以看到和Oracle一样,pg中简单的select操作也是可能会产生wal日志的!

下面我们来验证下:
首先需要知道,pg中通过表行的t_infomask结构来判断行的状态,其可取值为:
src/include/access/htup_details.h
在这里插入图片描述
下面我们创建一张测试表来验证:

bill=# create table test(id int);
CREATE TABLE
bill=# insert into test values(1);
INSERT 0 1
bill=# insert into test values(2);
INSERT 0 1
bill=# select t_xmin,t_xmax,t_infomask,t_infomask2 from heap_page_items(get_raw_page('test', 0));
 t_xmin | t_xmax | t_infomask | t_infomask2
--------+--------+------------+-------------
   1274 |      0 |       2048 |           1
   1275 |      0 |       2048 |           1
(2 rows)

可以看到这两行数据的t_infomask值为2048,即0x0800,表示的是t_xmax invalid/aborted,代表xmax的相应事务已经abort或者invalid。

接着我们查询下该表,按照前文所说:当我们第一次去查询时是会去clog中判断其事务状态并修改状态为,我们看下是否如此。

bill=# select * from test;
 id
----
  1
  2
(2 rows)

bill=# select t_xmin,t_xmax,t_infomask,t_infomask2 from heap_page_items(get_raw_page('test', 0));
 t_xmin | t_xmax | t_infomask | t_infomask2
--------+--------+------------+-------------
   1274 |      0 |       2304 |           1
   1275 |      0 |       2304 |           1
(2 rows)

可以看到t_infomask值变成了2304,即0x0900,可知是0x0800 | 0x0100 = 0x0900,原来是设置了HEAP_XMIN_COMMITTED这个标志位,为何我查询了数据之后会对t_infomask置位呢?那便是因为去clog中判断了这两行数据的事务状态并进行了修改。

接着我们继续看:

bill=# begin;
BEGIN
bill=*# update test set id = 100 where id = 1;
UPDATE 1
bill=*# select t_xmin,t_xmax,t_infomask,t_infomask2 from heap_page_items(get_raw_page('test', 0));
 t_xmin | t_xmax | t_infomask | t_infomask2
--------+--------+------------+-------------
   1274 |   1276 |        256 |       16385
   1275 |      0 |       2304 |           1
   1276 |      0 |      10240 |       32769
(3 rows)

bill=*# end;
COMMIT
bill=# select t_xmin,t_xmax,t_infomask,t_infomask2 from heap_page_items(get_raw_page('test', 0));
 t_xmin | t_xmax | t_infomask | t_infomask2
--------+--------+------------+-------------
   1274 |   1276 |        256 |       16385
   1275 |      0 |       2304 |           1
   1276 |      0 |      10240 |       32769
(3 rows)

我们再事务中update了id=1这行数据,可以看见在事务提交前后,数据的t_infomask并没有发生变化。

而接下来我们再次去查看该表:
可以看到t_infomask发生了改变。

bill=# select * from test;
 id
-----
   2
 100
(2 rows)

bill=# select t_xmin,t_xmax,t_infomask,t_infomask2 from heap_page_items(get_raw_page('test', 0));
 t_xmin | t_xmax | t_infomask | t_infomask2
--------+--------+------------+-------------
   1274 |   1276 |       1280 |       16385
   1275 |      0 |       2304 |           1
   1276 |      0 |      10496 |       32769
(3 rows)
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值