80

我在 pg 中有一个表格,如下所示:

CREATE TABLE t (
    a BIGSERIAL NOT NULL,               -- 8 b
    b SMALLINT,                         -- 2 b
    c SMALLINT,                         -- 2 b
    d REAL,                             -- 4 b
    e REAL,                             -- 4 b
    f REAL,                             -- 4 b
    g INTEGER,                          -- 4 b
    h REAL,                             -- 4 b
    i REAL,                             -- 4 b
    j SMALLINT,                         -- 2 b
    k INTEGER,                          -- 4 b
    l INTEGER,                          -- 4 b
    m REAL,                             -- 4 b
    CONSTRAINT a_pkey PRIMARY KEY (a)
);

以上加起来每行最多 50 个字节。我的经验是,我需要另外 40% 到 50% 的系统开销,甚至没有任何用户创建的上述索引。因此,每行大约 75 个字节。我将在表中有很多很多行,可能超过 1450 亿行,因此该表将推送 13-14 TB。如果有的话,我可以使用什么技巧来压缩这张桌子?我可能的想法如下......

real值转换为integer. 如果它们可以存储为smallint,则每个字段节省 2 个字节。

将列 b .. m 转换为数组。我不需要搜索这些列,但我确实需要能够一次返回一列的值。所以,如果我需要 g 列,我可以做类似的事情

SELECT a, arr[5] FROM t;

我会使用数组选项节省空间吗?会不会有超速处罚?

还有其他想法吗?

4

4 回答 4

230

“列俄罗斯方块”

实际上,你可以做一些事情,但这需要更深入的理解。关键字是对齐填充每种数据类型都有特定的对齐要求

您可以通过有利地对列进行排序来最大程度地减少因填充而损失的空间。以下(极端)示例会浪费大量物理磁盘空间:

CREATE TABLE t (
    e int2    -- 6 bytes of padding after int2
  , a int8
  , f int2    -- 6 bytes of padding after int2
  , b int8
  , g int2    -- 6 bytes of padding after int2
  , c int8
  , h int2    -- 6 bytes of padding after int2
  , d int8)

要每行节省24 个字节,请改用:

CREATE TABLE t (
    a int8
  , b int8
  , c int8
  , d int8
  , e int2
  , f int2
  , g int2
  , h int2)   -- 4 int2 occupy 8 byte (MAXALIGN), no padding at the end

db<>fiddle here
sqlfiddle

根据经验,如果您首先放置 8 字节列,然后放置 4 字节、2 字节和 1 字节列,则不会出错。

boolean, uuid(!) 和其他一些类型不需要对齐填充。textvarchar以及其他“varlena”(可变长度)类型名义上需要“int”对齐(在大多数机器上为 4 个字节)。但我观察到磁盘格式没有对齐填充(与 RAM 不同)。最终,我在源代码的注释中找到了解释:

另请注意,我们允许在存储“打包”varlenas 时违反名义对齐;TOAST 机制负责从大多数代码中隐藏它。

因此,仅当包含单个前导长度字节的(可能是压缩的)数据超过 127 个字节时,才强制执行“int”对齐。然后 varlena 存储切换到四个前导字节并需要“int”对齐。

通常,您最多可以在播放“列俄罗斯方块”时每行节省几个字节。在大多数情况下,这些都不是必需的。但是对于数十亿行,这可能意味着几千兆字节。

您可以使用函数测试实际的列/行大小pg_column_size()
某些类型在 RAM 中占用的空间比在磁盘上的空间多(压缩或“打包”格式)。当使用pg_column_size().

最后,某些类型可以被压缩或“烘烤”(离线存储)或两者兼而有之。

每个元组的开销(行)

项目标识符每行 4 个字节 - 不受上述注意事项的影响。
元组标头至少有 24 个字节(23 + 填充)。数据库页面布局手册:

有一个固定大小的标头(在大多数机器上占用 23 个字节),然后是一个可选的空位图、一个可选的对象 ID 字段和用户数据。

对于标头和用户数据之间的填充,您需要MAXALIGN在服务器上知道 - 通常在 64 位操作系统上为 8 个字节(或在 32 位操作系统上为 4 个字节)。如果您不确定,请查看pg_controldata

在Postgres 二进制目录中运行以下命令以获得明确的答案:

./pg_controldata /path/to/my/dbcluster

手册:

实际用户数据(行的列)从 指示的偏移量开始,该偏移量必须始终是 平台距离t_hoff的倍数。MAXALIGN

因此,您通常通过将数据打包成 8 个字节的倍数来获得最佳存储空间。

在您发布的示例中没有任何收获。已经包得很严实了。最后 2 个字节的填充,最后int24 个字节。您可以在最后将填充合并为 6 个字节,这不会改变任何内容。

每个数据页的开销

数据页大小通常为 8 KB。在这个级别上也有一些开销/膨胀:剩余部分不足以容纳另一个元组,更重要的是死行或FILLFACTOR设置保留的百分比。

磁盘大小还有几个其他因素需要考虑:

数组类型?

对于您正在评估的数组类型,您将为该类型添加24 字节的开销。另外,数组元素像往常一样占用空间。那里没有任何收获。

于 2011-09-15T13:14:07.477 回答
11

在一个数组中存储多个数值字段时,我看不出有什么好处(也没有什么可失去的)。

每个数字类型的大小都有明确的记录,您应该简单地使用与您所需的范围分辨率兼容的最小大小的类型;这就是你所能做的。

我不认为(但我不确定)是否对沿行的列有一些字节对齐要求,在这种情况下,列的重新排序可能会改变使用的空间 - 但我不这么认为。

顺便说一句,每行有一个固定开销,大约23 个字节

于 2010-06-03T14:01:10.503 回答
8

从这个伟大的文档: https ://www.2ndquadrant.com/en/blog/on-rocks-and-sand/

对于您已经拥有的表,或者您正在开发中的表,名为my_table,此查询将给出从左到右的最佳顺序。

SELECT a.attname, t.typname, t.typalign, t.typlen
FROM pg_class c
JOIN pg_attribute a ON (a.attrelid = c.oid)
JOIN pg_type t ON (t.oid = a.atttypid)
WHERE c.relname = 'my_table'
 AND a.attnum >= 0
ORDER BY t.typlen DESC
于 2020-01-01T15:43:16.803 回答
0

这是关于 Erwin 的列重新排序建议的一个很酷的工具:https ://github.com/NikolayS/postgres_dba

它有确切的命令——p1:

在此处输入图像描述

然后它会自动向您显示所有表上列重新排序的真正潜力:

在此处输入图像描述

于 2022-01-29T12:25:17.933 回答