0
点赞
收藏
分享

微信扫一扫

pg事务篇(三)—— 事务状态与Hint Bits(t_infomask)


一、 引入背景

前篇(pg事务篇(一)—— 事务与多版本并发控制MVCC_Hehuyi_In到,clog日志记录了事务的四种状态。

当需要获取事务状态时,pg可以通过调用三个内部函数——TransactionIdIsInProcess、TransactionIdDidCommit和TransactionIdDidAbort,读取CLOG返回所请求事务状态。可想而知,如果每次都对每条元组执行函数检查,效率不高,可能会有性能问题。为此pg引入了提示位 Hint Bits,提升获取事务状态的效率,进而加速可见性判断。

pg事务篇(三)—— 事务状态与Hint Bits(t_infomask)_元组

二、 Hint Bits

所谓Hint Bits,就是把事务状态直接记录在元组头中(HeapTupleHeaderData),避免频繁访问CLOG影响性能

pg事务篇(三)—— 事务状态与Hint Bits(t_infomask)_PostgreSQL_02

 Field

Type

Length

Description

t_xmin

TransactionId

4 bytes

insert XID stamp

t_xmax

TransactionId

4 bytes

delete XID stamp

t_cid

CommandId

4 bytes

insert and/or delete CID stamp (overlays with t_xvac)

t_xvac

TransactionId

4 bytes

XID for VACUUM operation moving a row version

t_ctid

ItemPointerData

6 bytes

current TID of this or newer row version

t_infomask2

uint16

2 bytes

number of attributes, plus various flag bits

t_infomask

uint16

2 bytes

various flag bits

t_hoff

uint8

1 byte

offset to user data

源码中位于 src/include/access/htup_details.h 文件

pg事务篇(三)—— 事务状态与Hint Bits(t_infomask)_postgresql_03

t_infomask的取值如下,基本上看源码注释就能知道每种取值的含义,具体案例在 一文带你搞懂PostgreSQL中的标志位 有详细介绍,这里不再展开

pg事务篇(三)—— 事务状态与Hint Bits(t_infomask)_元组_04

pg事务篇(三)—— 事务状态与Hint Bits(t_infomask)_元组_05

文章还分享了一个实用函数,可以直接获取infomask和infomask2里面的标志位信息

create type infomask_bit_desc as (mask varbit, symbol text);

create or replace function infomask(msk int, which int) returns text
language plpgsql as $$
declare
      r infomask_bit_desc;
      str text = '';
      append_bar bool = false;
begin
       for r in select * from infomask_bits(which) loop
               if (msk::bit(16) & r.mask)::int <> 0 then
                       if append_bar then
                              str = str || '|';
                      end if;
                      append_bar = true;
                      str = str || r.symbol;
              end if;
      end loop;
      return str;
end;
$$ ;

create or replace function infomask_bits(which int)
returns setof infomask_bit_desc
language plpgsql as $$
begin
       if which = 1 then
              return query values
              (x'8000'::varbit, 'MOVED_IN'),
              (x'4000', 'MOVED_OFF'),
              (x'2000', 'UPDATED'),
              (x'1000', 'XMAX_IS_MULTI'),
              (x'0800', 'XMAX_INVALID'),
              (x'0400', 'XMAX_COMMITTED'),
              (x'0200', 'XMIN_INVALID'),
              (x'0100', 'XMIN_COMMITTED'),
              (x'0080', 'XMAX_LOCK_ONLY'),
              (x'0040', 'EXCL_LOCK'),
              (x'0020', 'COMBOCID'),
              (x'0010', 'XMAX_KEYSHR_LOCK'),
              (x'0008', 'HASOID'),
              (x'0004', 'HASEXTERNAL'),
              (x'0002', 'HASVARWIDTH'),
              (x'0001', 'HASNULL');
      elsif which = 2 then
              return query values
              (x'2000'::varbit, 'UPDATE_KEY_REVOKED'),
              (x'4000', 'HOT_UPDATED'),
              (x'8000', 'HEAP_ONLY_TUPLE');
      end if;
end;
$$;

使用方法

create table nullt1(id int,info text);
insert into nullt1 values(1,'hehuyi');
insert into nullt1 values(1,null);
insert into nullt1 values(null,null);

select lp, t_xmin, t_xmax, t_ctid,
      infomask(t_infomask, 1) as infomask,
      infomask(t_infomask2, 2) as infomask2
from heap_page_items(get_raw_page('nullt1', 0));

lp | t_xmin | t_xmax | t_ctid |         infomask         | infomask2
----+---------+--------+--------+--------------------------+-----------
 1 | 3643895 |      0 | (0,1) | XMAX_INVALID|HASVARWIDTH |
 2 | 3643896 |      0 | (0,2) | XMAX_INVALID|HASNULL     |
 3 | 3643897 |      0 | (0,3) | XMAX_INVALID|HASNULL     |

三、 t_infomask的计算

create table test(id int);
insert into test values(1);
insert into test values(2);

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
--------+--------+------------+-------------
    526 |      0 |       2048 |           1
    527 |      0 |       2048 |           1

这里可以看到,t_infomask的值为2048。2048 = 2^11,也就是0x0800,对比前面所说的t_infomask,也就是HEAP_XMAX_INVALID,代表xmax的相应事务已经abort或者invalid,这个时候我们简单查一下数据:

explain analyze select id from test where id = 1;

                                  QUERY PLAN                                           
------------------------------------------------------------------------------------------------
 Seq Scan on test  (cost=0.00..41.88 rows=13 width=4) (actual time=0.012..0.013 rows=1 loops=1)
   Filter: (id = 1)
   Rows Removed by Filter: 1
 Planning Time: 0.054 ms
 Execution Time: 0.042 ms

再次查询标志位

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
--------+--------+------------+-------------
    526 |      0 |       2304 |           1
    527 |      0 |       2304 |           1

t_infomask的值为2304了,也就是0x0900,对比一下t_infomask,可知是0x0800 | 0x0100 = 0x0900。也就是又设置了HEAP_XMIN_COMMITTED(0x0100)这个标志位,为何查询数据之后会对t_infomask置位呢?

就回到我们最前面说的,当查询一条数据的时候,需要去判断行的可见性,需要去查询相应事务的提交状态。元组中的 Hint Bits采用延迟更新策略,并不会在事务提交或者回滚时主动更新所有操作过的元组Hint Bits。

等到第一次访问(可能是VACUUM,DML或SELECT)该元组并进行可见性判断时:

  • 如果Hint Bits已设置,直接读取Hint Bits的值。
  • 如果Hint Bits未设置,则调用函数从CLOG中读取事务状态。如果事务状态为COMMITTED或ABORTED,则将Hint Bits设置到元组的t_informask字段。如果事务状态为INPROCESS,由于其状态还未到达终态,无需设置Hint Bits。

Hint Bits可以理解为是事务状态在元组头上的一份缓存,减少访问链路的长度,让事务状态触手可及。

四、 Hint Bits与日志

如 PostgreSQL checksum 一文所说,在开启CHECKSUM或者wal_log_hints=true的情况下,如果CHECKPOINT后第一次使页面dirty的操作是更新Hint Bits,则会产生一条WAL日志,将当前数据块写入WAL日志中(Full Page Image),避免产生部分写,导致数据CHECKSUM异常。

因此,在开启CHECKSUM或者wal_log_hints=true时,即便执行SELECT,也可能更改页面的Hint Bits,从而导致产生WAL日志,这会在一定程度上增加WAL日志占用的存储空间。如果在使用pg中发现执行SELECT会触发磁盘的写入操作,可以检查一下是否开启了CHECKSUM或者wal_log_hints。

注意,以上写FullPageImage日志的行为与是否开启full_page_writes没有关系。相关代码实现可以参考MarkBufferDirtyHint这个函数。

参考

PostgreSQL: Documentation: 9.6: Database Page Layout

PgSQL · 引擎特性 · PostgreSQL Hint Bits 简介

PostgreSQL Tuple中t_infomask的思考

一文带你搞懂PostgreSQL中的标志位

《PostgreSQL指南 内幕探索》

举报

相关推荐

0 条评论