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

相关推荐
睡不醒男孩0308235 小时前
PostgreSQL 数据库运维转型:从传统模式到 CLup 平台的 25 个核心 FAQ
运维·数据库·postgresql
JOJO数据科学6 小时前
pgAdmin4 Electron 鸿蒙 PC 适配全记录:从白屏到连接 PostgreSQL
postgresql·electron·harmonyos
日取其半万世不竭7 小时前
PostgreSQL 跑在 Docker 里怎么备份?恢复成功才算备份成功
数据库·docker·postgresql
倒流时光三十年8 小时前
PostgreSQL LEAST 表达式函数详解
数据库·postgresql
Rain5099 小时前
2.4. PostgreSQL 数据库连接与实战指南
前端·数据库·人工智能·后端·postgresql·数据分析
倒流时光三十年1 天前
PostgreSQL CASE 条件表达式详解
数据库·postgresql
倒流时光三十年1 天前
PostgreSQL COALESCE 条件表达式函数详解
数据库·postgresql
雁無痕1 天前
Postgresql启动无监听端口问题的解决
postgresql
倒流时光三十年1 天前
PostgreSQL NULLIF 条件表达式函数详解
数据库·sql·postgresql
倒流时光三十年1 天前
PostgreSQL VALUES 列表详解
数据库·postgresql