有一篇流行且看似权威的博客文章On Rocks and Sand关于如何优化 PostgreSQL 表的大小以通过重新排序列长度来消除内部填充。他们解释了如果可变长度类型不在表格末尾,它们如何产生一些额外的填充:
这意味着我们可以整天链接可变长度的列,而无需在右侧边界处引入填充。因此,我们可以推断出可变长度列不会引入膨胀,只要它们位于列列表的末尾。
在文章的最后,总结一下:
按照 pg_type 中定义的类型长度对列进行排序。
有一个与 Ruby 的 ActiveRecord 集成的库,可以自动重新排序列以减少填充,称为pg_column_byte_packer。您可以在该 repo 中看到 README 引用了上述博客文章,并且通常与博客文章描述的内容相同。
但是,pg_column_byte_packer
返回的结果与它引用的博客文章不一致。这篇博文取自 PostgreSQL 的内部pg_type.typelen
,它通过 -1 的对齐方式将可变长度列始终放在末尾。pg_column_byte_packer
给他们一个对齐3。
pg_column_byte_packer
有解释性评论:
# These types generally have an alignment of 4 (as designated by pg_type
# having a typalign value of 'i', but they're special in that small values
# have an optimized storage layout. Beyond the optimized storage layout, though,
# these small values also are not required to respect the alignment the type
# would otherwise have. Specifically, values with a size of at most 127 bytes
# aren't aligned. That 127 byte cap, however, includes an overhead byte to store
# the length, and so in reality the max is 126 bytes. Interestingly TOASTable
# values are also treated that way, but we don't have a good way of knowing which
# values those will be.
#
# See: `fill_val()` in src/backend/access/common/heaptuple.c (in the conditional
# `else if (att->attlen == -1)` branch.
#
# When no limit modifier has been applied we don't have a good heuristic for
# determining which columns are likely to be long or short, so we currently
# just slot them all after the columns we believe will always be long.
评论似乎没有错,因为文本列的 apg_type.typalign
值为 4,但它们也有pg_type.typlen
-1 的值,博客文章认为在表格末尾时获得最佳包装。
因此,对于具有一integer
列、一text
列和一smallint
列的表,pg_column_byte_packer
会将文本列放在两者之间。他们甚至有一个单元测试来断言这种情况总是会发生。
我的问题是:什么列的顺序实际上是为最小的空间打包的?来自的评论pg_column_byte_packer
似乎没有错,因为文本列的 apg_type.typalign
值为 4,但它们的 apg_type.typlen
值为 -1。
当我几年前研究它时,我也感到困惑。
typlen = -1
只是表示varlena
存储,名义上具有typalign = 'i'
(整数对齐,需要从4字节偏移开始)。但这还不是全部。最终,我在源代码的注释中找到了解释:因此,
varlena
数据 < 127 字节(在可能的压缩之后)仅增加 1 字节的开销(表示其长度)并且不需要“磁盘上”的对齐填充。(这些天几乎没有任何“磁盘”了。)看:
要回答标题中的问题:
真实的。超过 127 字节的数据不能以优化的形式存储,并退回到需要“整数对齐”。如果我们不知道 varlena 类型的列将保持在该阈值以下(大部分时间),我们不能肯定地说。
此外,优化存储还有其他注意事项。一行中有许多列,首先使用
NOT NULL
固定大小长度的列计算元组存储偏移量会更便宜。首先放置经常访问的列也产生了一个微小的优势。所有这些都因 TOAST 机制和 index(-only) 访问而变得更加复杂。但所有这些影响通常都很微小。相比之下,当一列占用 200 个字节时,对齐填充丢失 3 个字节就显得相形见绌了。所以大多不值得费心。经验法则涵盖了大部分内容:
按所需的对齐方式对列进行排序
typalign
:d
-->i
-->s
-->c
。但是
typlen = -1
("varlena") 最后(通常),即使是正式typalign = 'i'
的 .手册:
你的例子
pg_column_byte_packer
名副其实。int
-->text
-->smallint
尽可能地紧。对于短字符串的典型情况,唯一相关的决定是
int
放在第一位。smallint
最多可以在奇数字节偏移处强制 1 个额外字节的对齐填充。由于元组空间总是以 8 字节的倍数分配,这永远不会导致更大的元组。超过磁盘上 127 字节阈值的字符串(包括 1 个前导长度字节),翻转到 4 个前导长度字节并需要标称
integer
对齐。这就是放在text
beforesmallint
可以有效地保护 8 个字节的地方。随机字符串长度发生在 25% 的情况下,因此对于至少 144 个字节的元组,平均 2 个字节。就是这样。但是放在第
smallint
一个通常具有微小的优势,并且大多数text
列都远低于长度阈值。要记住的是不要穿插多个
smallint
和text
列。在这种情况下,可以叠加多个偏移量。