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 性能。

相关推荐
XiaoMu_0012 天前
MySQL、PostgreSQL、MongoDB和Redis全面对比
mysql·mongodb·postgresql
IvorySQL2 天前
直播预告| PostgreSQL 与 IvorySQL 在云原生时代的演进与实践
数据库·postgresql·ivorysql
God写代码没有注释3 天前
SQLCipher数据迁移到PostgreSql详细攻略
postgresql
IvorySQL4 天前
PostgreSQL 全表 count 优化实践:从 SeqScan 痛点分析到 heapam 改进与性能突破
数据库·postgresql·oracle·deepseek·ivorysql
IvorySQL10 天前
IvorySQL 4.6:DocumentDB+FerretDB 实现 MongoDB 兼容部署指南
postgresql
l1t11 天前
利用DeepSeek实现服务器客户端模式的DuckDB原型
服务器·c语言·数据库·人工智能·postgresql·协议·duckdb
小兜全糖(xdqt)12 天前
pyspark 从postgresql读取数据
数据库·postgresql
心随_风动12 天前
Ubuntu 文件复制大师:精通cp命令完整指南
数据库·ubuntu·postgresql
IvorySQL12 天前
PostgreSQL 上的向量搜索实践
postgresql·llm