当向表中插入数据时,可以指定列的值为null。例如,有一张表t(i int,j int,k int),我们可以插入值(8,1,6),或将包含null值的(3,null,7)插入到表中。本文将探讨PostgrSQL中是如何存储null的一些技术细节。
PostgreSQL如何存储null值的?
PostgreSQL中的一个元组被分成元组头部分和数据部分。上面提到了向表t插入两条数据的例子。这里,一幅图片被用来抛出这个问题:在元组中null值是如何展示的?
如图中所示,一个int类型的数据占据4个字节的空间,但是遇到null值时,数据库该如何表示在3和7之间有个null值呢?
PostgreSQL存储null值的方法
使用pageinspact工具来观察null是如何存储的。执行下面的测试:
postgres=# create table t(i int, j int, k int);
CREATE TABLE
postgres=# insert into t values(8,1,6);
INSERT 0 1
postgres=# insert into t values(3,NULL,7);
INSERT 0 1
postgres=# insert into t values(4,9,2);
INSERT 0 1
postgres=# select lp,t_infomask,t_bits,t_data from heap_page_items(get_raw_page('t', 0));
lp | t_infomask | t_bits | t_data
----+------------+----------+----------------------------
1 | 2048 | | \x080000000100000006000000
2 | 2049 | 10100000 | \x0300000007000000
3 | 2048 | | \x040000000900000002000000
(3 rows)
postgres=#
可以看到,null值没有在元组的数据部分标记出来。
同时,可以看到带有null值的第二条记录的't_infomask'和’t_bits‘的值与第一条和第三条是不一致的。因此,这可能就是如何读取null值的秘密所在。
't_bits'设计
‘t_bits’是一个uint8的数组,当元组中没有null值的时候,t_bits可以被认为是空的,当元组有null值的列时,t_bits使用一个位(bit)来表示一个列是否为null。
postgres=# create table t0(i1 int,i2 int,i3 int,i4 int,i5 int,i6 int,
postgres(# i7 int,i8 int,i9 int,i10 int,i11 int,i12 int
postgres(# );
CREATE TABLE
postgres=# insert into t0 values(1,2,3,4,5,6,7,8,9,10,NULL,12);
INSERT 0 1
postgres=# insert into t0 values(1,2,3,4,5,6,7,8,9,NULL,NULL,12);
INSERT 0 1
postgres=# insert into t0 values(1,2,3,4,5,6,7,8,NULL,NULL,NULL,12);
INSERT 0 1
postgres=# insert into t0 values(1,2,3,4,5,6,7,8,9,10,11,12);
INSERT 0 1
postgres=#
postgres=# select lp,t_infomask,t_bits from heap_page_items(get_raw_page('t0', 0));
lp | t_infomask | t_bits
----+------------+------------------
1 | 2049 | 1111111111010000
2 | 2049 | 1111111110010000
3 | 2049 | 1111111100010000
4 | 2048 |
(4 rows)
postgres=#
上面的脚本会创建一个表t0,共有12个列,然后插入几条元组,执行pageinspect工具查询研究元组。注意看元组的t_bits值:
可以得出结论:
·在t_bits中,尚未使用的标记为是0
·1表示对应的列是非空的(not null),否则就是null
表中被删除的列
在PostgreSQL中,表的列被删除后,这个列的数据结构会被保留在目录中,但是对用户是不可见的。完成下面的测试:
postgres=# alter table t0 drop column i1;
ALTER TABLE
postgres=# insert into t0 values(2,3,4,5,6,7,8,9,10,11,12);
INSERT 0 1
postgres=# select lp,t_infomask,t_bits from heap_page_items(get_raw_page('t0', 0));
lp | t_infomask | t_bits
----+------------+------------------
1 | 2049 | 1111111111010000
2 | 2049 | 1111111110010000
3 | 2049 | 1111111100010000
4 | 2048 |
5 | 2049 | 0111111111110000
(5 rows)
postgres=#
在这个测试中,我们删除了表t0的列'i1'并插入一个不含有null值的记录。使用pageinspact工具,我们可以发现插入的不含有null值的数据(lp=5)。被删除的列的t_bits值显示其也被认为是null值。
当记录没有任何空值时,t_bits不会存储在记录中。一旦存在空值,t_bits将存储在元组中,以记录所有列的空值状态。表的删除列被视为空列。因此,当表中有许多列时,请勿删除列,否则将为每条记录生成额外的t_bit,这将导致存储膨胀。