postgresql源码学习(55)—— 列中的NULL值是如何存储和判断的?

问题来自 《PostgreSQL面试题集锦》学习与回答_Hehuyi_In的博客-CSDN博客 第11题

一、 NULL值存储位置

postgresql源码学习(55)—— 列中的NULL值是如何存储和判断的?_第1张图片

       在pg元组头数据中,有一个t_bits数组,用于存储空值位图。当元组中没有null值的时候,t_bits可以被认为是空的,当元组有null值的列时,t_bits使用一个bit来表示列是否为null。

htup_details.h

struct HeapTupleHeaderData
{
…
    ItemPointerData t_ctid;     /* current TID of this or newer tuple (or a
                                 * speculative insertion token) */
…
    bits8       t_bits[FLEXIBLE_ARRAY_MEMBER];  /* bitmap of NULLs */
};

FLEXIBLE_ARRAY_MEMBER默认为空

#define FLEXIBLE_ARRAY_MEMBER   /* empty */

判断字段是否为空代码在 tupmacs.h,留待后面研究

/*
 * Check a tuple's null bitmap to determine whether the attribute is null.
 * Note that a 0 in the null bitmap indicates a null, while 1 indicates
 * non-null.
 */
#define att_isnull(ATT, BITS) (!((BITS)[(ATT) >> 3] & (1 << ((ATT) & 0x07))))

二、 pageinspact观察空值存储

创建测试表

create table t(a int,b int,c int);

insert into t values(4,7,1);
insert into t values(6,NULL,3);

postgres=# select * from t;
 a | b | c 
---+---+---
 4 | 7 | 1
 6 |   | 3
(2 rows)

pageinspact可以观察空值是如何存储的,infomask函数的定义参考:pg事务篇(三)—— 事务状态与Hint Bits(t_infomask)_access/htup_details.h:没有那个文件或目录_Hehuyi_In的博客-CSDN博客

select lp,infomask(t_infomask, 1) as infomask,t_bits,t_data from  heap_page_items(get_raw_page('t',0));

  • t_infomask可以看出其包含空值
  • t_bits数组不为空:10100000,第一和第三个1表示这两列不为空,中间红色的0表示第二列为空,其余的0表示这些列未被使用。
  • t_data中则不包含空值数据

为了看t_bits数组更清晰,我们加多几列

create table t0(i1 int,i2 int,i3 int,i4 int,i5 int,i6 int, i7 int,i8 int,i9 int,i10 int,i11 int,i12 int);
insert into t0 values(1,2,3,4,5,6,7,8,9,10,NULL,12);
insert into t0 values(1,2,3,4,5,6,7,8,9,NULL,NULL,12);
insert into t0 values(1,2,3,4,5,6,7,8,NULL,NULL,NULL,12);
insert into t0 values(1,2,3,4,5,6,7,8,9,10,11,12);

select t_bits from  heap_page_items(get_raw_page('t0', 0));

      t_bits     

------------------

 1111111111010000

 1111111110010000

 1111111100010000

(4 rows)

  • 第四行没有空值,因此对应t_bits数组为空
  • 一共12列,因此数组中后四位均未用到,为0
  • 红色的3个0分别对应前三行中3个null值的列位置

观察删掉其中一列的效果

alter table t0 drop column i1;
select t_bits from  heap_page_items(get_raw_page('t0', 0));

postgresql源码学习(55)—— 列中的NULL值是如何存储和判断的?_第2张图片

发现位图并没有变化

再插入一行非空值

insert into t0 values(2,3,4,5,6,7,8,9,10,11,12);
select t_bits from  heap_page_items(get_raw_page('t0', 0));

postgresql源码学习(55)—— 列中的NULL值是如何存储和判断的?_第3张图片

     可以看到,表中已删除列会被视为空列。当表中有许多列时,删除列将为每条记录生成额外的t_bit,这将导致存储膨胀。

三、 源码如何根据t_bits数组判断列是否为空

前面有提到过,判断字段是否为空代码如下

/*
 * Check a tuple's null bitmap to determine whether the attribute is null.
 * Note that a 0 in the null bitmap indicates a null, while 1 indicates
 * non-null.
 */
#define att_isnull(ATT, BITS) (!((BITS)[(ATT) >> 3] & (1 << ((ATT) & 0x07))))

1. 不超过8个字段

t表为例,第二行t_bits数组值为10100000,注意实际存储的时候值是颠倒的,所以是00000101

postgres=# select * from t;

 a | b | c

---+---+---

4 | 7 | 1

6 |   | 3

以第1列为例,ATT是列号(从0开始)

  • BITS[ATT>>3]计算该列在数组的下标,第0~7列下标都为0,例如BITS[0 >> 3] = BITS[0]
  • 1 << ((ATT) & 0x07)) 求字段顺序,例如0 & 0x07 = 0,再左移1位为1
  • !(BITS[0] & 1) = !( 00000101 & 00000001) = !(1) = 0
  • att_isnull函数返回0,所以第一列非空,与实际一致

2. 超过8个字段

insert into t0 values(1,2,3,4,5,6,7,8,9,10,NULL,12);

t_bits数组值为1111111111010000,颠倒后为0000101111111111

以第11为例,其ATT=10,即00001010

  • BITS[ATT>>3]计算该列在数组的下标BITS[(00001010) >> 3] = BITS[1]
  • 1 << ((ATT) & 0x07)) 求字段顺序,例如00001010 & 0x07 = 000000102),再左移1位为000001004
  • !(BITS[1] & 4) = !(00001011& 00000100) = !(0) = 1
  • att_isnull函数返回0,所以第10列为空,与实际一致

参考

https://www.cnblogs.com/abclife/p/13855150.html

Postgres是如何管理空值的

你可能感兴趣的:(源码学习,PostgreSQL,内部存储,postgresql,源码学习,NULL值,空值,存储)