PostgreSQL列打包省空间的判断依据:是数据类型对齐还是类型大小?
PostgreSQL列对齐的空间节省规则本质
核心结论非常明确:列排列实现空间节省的判断依据,是每个数据类型在系统中定义的强制对齐要求,而非数据类型本身的存储尺寸。你在教程中观测到的“相同整数尺寸列连续排列能省空间”,只是特定类型集合下的巧合,并非通用规则。
底层运行逻辑
PostgreSQL的行数据(元组)在磁盘页内存储时,为了匹配CPU的原生内存访问效率,会严格参照每个数据类型在系统表中定义的两个独立属性计算存储空间:
- 类型实际存储长度:决定该列占用的原始字节数
- 类型强制对齐边界:规定该列的起始存储偏移量必须是边界值的整数倍;如果前序列的累计偏移不满足要求,系统会自动插入无实际意义的0填充字节(即对齐padding)补齐偏移,这类填充字节就是纯浪费的存储空间。
为什么会产生“按尺寸排列就能省空间”的直觉
绝大多数内置固定长度数值类型的对齐要求,刚好和自身存储尺寸完全匹配:
- 2字节长度的
int2类型,对齐要求为2字节边界 - 4字节长度的
int4、date类型,对齐要求为4字节边界 - 8字节长度的
int8、float8、timestamp类型,对齐要求为8字节边界
这种场景下,把同尺寸的列连续排列,自然不会产生多余的对齐填充,看起来就像是“按尺寸归类就能省空间”。
为什么说判断依据绝非存储尺寸
当遇到对齐要求和自身尺寸不匹配的类型时,单纯按尺寸排列的逻辑会直接失效:
- 比如
macaddr8类型,实际存储长度为8字节,但系统定义的对齐要求仅为2字节边界。哪怕它的尺寸和int8、timestamp这类8字节类型完全一致,也不需要凑8字节的整数倍偏移就能正常存储。 - 比如变长类型
numeric,实际存储长度会随输入值大小动态变化,但系统固定要求它必须满足4字节边界对齐,哪怕实际只占3字节存储空间,后续的列如果要求4字节对齐,依然会先补1个填充字节再存储。
举个直观的计算示例,三类列的属性分别为:char(1)(1字节长度,1字节对齐)、macaddr8(8字节长度,2字节对齐)、int4(4字节长度,4字节对齐):
如果完全按照“尺寸相同挨在一起”的逻辑排列,把macaddr8和int4这两个大尺寸列放在前面,最后放1字节的char(1):
- 起始偏移0放
int4(满足4字节对齐,占4字节) - 偏移4放
macaddr8(满足2字节对齐要求,不需要补填充,占8字节) - 偏移12放
char(1)(满足1字节对齐要求,占1字节)
整个过程没有任何多余padding,总元组长度13字节。
如果陷入“列是多大尺寸,就必须从自身尺寸的倍数偏移位置开始放”的认知误区,会误以为macaddr8作为8字节类型,必须从8的倍数偏移开始存储:
- 起始偏移0放
int4(占4字节) - 因为错误认为
macaddr8需要8字节对齐,就从偏移4开始补4字节无意义填充,到偏移8的位置放macaddr8(占8字节) - 偏移16放
char(1)(占1字节)
总元组长度直接变成21字节,凭空多了8字节的无效填充——本质就是把存储尺寸和对齐要求两个独立属性搞混了。
实际上PostgreSQL的官方行存储优化逻辑,从来不会按列的存储尺寸做归类,只会按列的对齐边界从高到低排列,从根源上减少对齐填充带来的空间浪费。
内容的提问来源于stack exchange,提问作者jian
相关产品推荐
相关产品推荐

