PostgreSQL高效建表存储数据对齐问题

磁盘上的数据布局与 RAM 中的数据表示完全一致。页面及其元组按原样读入缓冲区缓存中,无需任何转换。这就是为什么数据文件在不同平台之间不兼容的原因

  • 不兼容的原因之一字节序 。例如,x86 架构是小端序 ,z/Architecture 是大端序 ,而 ARM 的字节序是可配置的
  • 另一个原因是按照机器字边界进行数据对齐,这也是许多架构所要求的。例如,在 32 位 x86 系统中,整数 (integer 类型,占用四个字节) 按四字节字边界对齐,就像双精度浮点数 (double precision 类型,占用八个字节)。但在 64 位系统中,双精度数按八字节字边界对齐。

数据对齐使得元组的大小取决于表中字段的顺序。这种影响通常可以忽略不计,但在某些情况下,它会导致大小显著增加。

使用pageinspect 扩展中的 heap_page_items 函数来显示关于指针和元组的一些细节。

sql 复制代码
=> CREATE TABLE padding(
  b1 boolean,
  i1 integer,
  b2 boolean,
  i2 integer
);
=> INSERT INTO padding VALUES (true,1,false,2);
=> SELECT lp_len FROM heap_page_items(get_raw_page('padding', 0));
 lp_len
−−−−−−−−
     40
(1 row)
​
#这一行的大小是 40 个字节,其行头占 24 个字节,integer 类型的列占用 4 个字节,每个 boolean 类型的列占用 1 个字节。总共 34 个字节,因此浪费了 6 个字节用于整数列的四字节对齐。
​
#如果我们重建表,空间将被更有效地利用:
=> DROP TABLE padding;
=> CREATE TABLE padding(
  i1 integer,
  i2 integer,
  b1 boolean,
  b2 boolean
);
=> INSERT INTO padding VALUES (1,2,true,false);
=> SELECT lp_len FROM heap_page_items(get_raw_page('padding', 0));
 lp_len
−−−−−−−−
     34
(1 row)
​

在 PostgreSQL 中,表通常被称为堆 (heap)。这是另一个晦涩的术语,暗示了元组的空间分配和动态内存分配之间的相似性。确实可以看到某种类比,但表由完全不同的算法管理。与有序索引相比,我们可以将这个术语理解为"一切都堆积成堆"。

关键概念:字节对齐

  • 4 字节对齐:数据必须存储在能被 4 整除的内存地址上
  • 填充字节:为满足对齐要求而插入的空字节

第一种表结构(低效)

ini 复制代码
CREATE TABLE padding(
  b1 boolean,    -- 1 字节
  i1 integer,    -- 4 字节,需要 4 字节对齐
  b2 boolean,    -- 1 字节  
  i2 integer     -- 4 字节,需要 4 字节对齐
);
​
内存布局分析:
偏移量:  0    1    2    3    4    5    6    7    8    9   10   11   12   13   14   15
内容:   [行头................................] [b1] [pad] [pad] [pad] [i1...........]
        
偏移量: 16   17   18   19   20   21   22   23   24   25   26   27   28   29   30   31
内容:   [b2] [pad] [pad] [pad] [i2...........] [未使用的空间...................]
​
总计: 24(行头) + 1(b1) + 3(填充) + 4(i1) + 1(b2) + 3(填充) + 4(i2) = 40 字节
​
浪费了6个字节

第二种表结构(高效)

sql 复制代码
CREATE TABLE padding(
  i1 integer,    -- 4 字节,4 字节对齐
  i2 integer,    -- 4 字节,4 字节对齐  
  b1 boolean,    -- 1 字节
  b2 boolean     -- 1 字节
);
​
内存布局分析:
偏移量:  0    1    2    3    4    5    6    7    8    9   10   11   12   13   14   15
内容:   [行头................................] [i1...........] [i2...........]
        
偏移量: 16   17   18   19   20   21   22   23   24   25   26   27   28   29   30   31
内容:   [b1] [b2] [未使用的空间................................................]
​
总计: 24(行头) + 4(i1) + 4(i2) + 1(b1) + 1(b2) = 34 字节
​

PostgreSQL 在存储表行时,会根据每个数据类型的对齐要求插入填充字节以满足对齐规则。整数类型(integer)需要 4 字节对齐,因此如果布尔类型(boolean,1 字节)列放在整数列之前或中间,会产生填充字节浪费空间。

  • 原始表结构:布尔类型列散布在整数列中间,导致每个整数列前都可能插入填充字节,使行长度增加至 40 字节。
  • 重新排序字段:先放所有整数类型,再放布尔类型,避免了中间填充,数据紧凑排列,使行长度减少到 34 字节。

因此,合理设计列的顺序,以满足对齐要求,可以显著减少行占用的存储空间,提高存储效率和 IO 性能。

相关推荐
周小码11 小时前
pgroll:简化PostgreSQL零停机迁移
数据库·postgresql
keep__go14 小时前
postgresql9.2.4 离线安装
linux·运维·数据库·postgresql
IvorySQL14 小时前
当数据库宕机时,PostgreSQL 高可用在背后做了什么?
数据库·postgresql
盒马coding14 小时前
PostgreSQL与SQL Server:B树索引差异及去重的优势
数据库·postgresql
kiwixing1 天前
集群无法启动CRS-4124: Oracle High Availability Services startup failed
java·运维·数据库·mysql·postgresql·oracle
❀͜͡傀儡师1 天前
Docker 运行 PolarDB-for-PostgreSQL 的命令,并已包含数据持久化配置
docker·postgresql·容器
IvorySQL1 天前
PostgreSQL 中唯一索引的工作原理
数据库·postgresql
king_harry2 天前
Postgresql客户端psql提示符(Prompt)配置
数据库·postgresql·prompt