理解Postgres行大小的含义

28

我有一张超过100M行的Postgres表,其结构为{整数, 整数, 整数, 不带时区的时间戳}。我预期每行的大小应为3*整数 + 1*时间戳 = 3*4 + 1*8 = 20字节。

实际上,每行大小为pg_relation_size(tbl) / count(*) = 52字节。为什么?

(没有对表进行删除操作:pg_relation_size(tbl, 'fsm') ~= 0)

2个回答

62

行大小的计算比这复杂得多。

存储通常分为8kB的数据页。每个页面有一个小的固定开销、可能剩余的大小不足以放下另一个元组,更重要的是死亡行或最初使用FILLFACTOR设置保留的百分比。

每一行(元组)也有更多的开销:页面开始处的4字节项标识符、23字节的HeapTupleHeader和对齐填充。元组头开始处以及元组数据开始处都以MAXALIGN的倍数对齐,典型的64位机器上为8字节。某些数据类型需要按2、4或8字节的下一个倍数对齐。

引用系统表pg_tpye上的手册:

typalign是存储此类型值时所需的对齐方式。它适用于磁盘上的存储以及PostgreSQL内部的大多数表示。当多个值连续存储时,例如在磁盘上完整行的表示中,在此类型的数据之前插入填充,以便它从指定边界开始。对齐参考是序列中第一个数据的开始处。

可能的值包括:

  • c = char对齐,即不需要对齐。

  • s = short对齐(大多数机器上为2字节)。

  • i = int对齐(大多数机器上为4字节)。

  • d = double对齐(许多机器上为8字节,但绝非全部)。

在这里阅读手册中的基础知识

您的示例

这将导致你的三个integer列后面有4个字节的填充,因为timestamp列需要double对齐,并且需要从下一个8字节的倍数开始。

因此,一行占据了:

   23   -- heaptupleheader
 +  1   -- padding or NULL bitmap
 + 12   -- 3 * integer (no alignment padding here)
 +  4   -- padding after 3rd integer
 +  8   -- timestamp
 +  0   -- no padding since tuple ends at multiple of MAXALIGN

A.H.在评论中指出,每个元组在页面头部都有一个加号的标识符:

 +  4   -- item identifier in page header
------
 = 52 bytes

因此,我们得到了观察到的52个字节

计算pg_relation_size(tbl) / count(*)是一种悲观的估计。 pg_relation_size(tbl)包括膨胀(死行)和由fillfactor保留的空间,以及每个数据页和每个表的开销。(我们甚至没有提到在TOAST表中压缩长varlena数据,因为这里不适用。)

您可以安装附加模块pgstattuple并调用SELECT * FROM pgstattuple('tbl_name');获取有关表和元组大小的更多信息。

相关内容:


2
那么,Postgres对于具有非常短的行(例如,几个整数)的大型表并不是很好。28字节的开销将始终使其膨胀。您是否知道当Postgres在缓存中保存这些表时是否会对其进行压缩? - Arman
2
在每个块中,难道不是还有每行的附加开销吗:指向实际元组头的ItemData指针(4字节)? - A.H.
@A.H.:说得好。虽然不是元组本身的一部分,但ItemData指针是按照每个元组在页面头中分配的,这应该解释了我计算的48字节和观察到的52字节磁盘空间之间的差异。我在我的答案中添加了一条注释。 - Erwin Brandstetter
2
@Arman:RAM中数据的表示需要更多的空间,因此不会进行压缩。如果您有长字符字符串,则它们将被压缩并可能被“TOAST”。在此处手册中了解有关TOAST的更多信息。因此,非常小的元组存在相当大的开销。尽管如此,表上的操作通常非常快,因此不要陷入过早去规范化您的表的诱惑中。如果有疑问,请运行性能测试。 - Erwin Brandstetter
@ErwinBrandstetter:嗯,那我们该如何理解这个问题呢:一个拥有400M RAM的Postgres服务器,带有一个4G的数据库(1个表),轻松处理30K cpm的负载。大约50%的读取/50%的插入操作都是针对这个表进行的。它如何可能通过磁盘读写来处理这么多的负载呢? - Arman
表格不需要全部缓存。此外,这里还有大量其他因素.. - Erwin Brandstetter

6
每行都有与之关联的元数据。正确的公式是(假设为朴素对齐):
3 * 4 + 1 * 8 == your data
24 bytes == row overhead
total size per row: 23 + 20

大约是53个字节。我实际上写了postgresql-varint来帮助解决这个问题,正好符合这个使用场景。您可能需要查看类似的帖子以获取有关元组开销的其他详细信息。


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接