【问题标题】:Is it possible to store a 1 byte number in Postgres?是否可以在 Postgres 中存储一个 1 字节的数字?
【发布时间】:2018-03-19 06:36:30
【问题描述】:

我想在 Postgres 中存储每条记录的 7 个 8 位整数值。 Pg 不提供单字节整数类型 SMALLINT 或 2 字节,这是最小的整数数据类型。无论如何我可以存储我的 7 个 8 位数字并节省空间吗?

具有 7 个元素数组的数组类型会更紧凑吗?或者,我应该对我的 7 个数字进行二进制表示(例如,在 Perl 中使用 pack)并将其存储在单个 bytea 字段中?

还有其他建议吗?

【问题讨论】:

标签: postgresql


【解决方案1】:

鉴于 PostgreSQL 中任何行的开销都是 23 bytes (HeapTupleHeaderData),如果您真的如此关心少量空间,那么您可能选择了错误的数据存储方式。

无论如何,由于所有更复杂的类型都有自己的开销(bytea 增加了 4 个字节的开销,例如,位字符串 5 到 8),完成您正在寻找的唯一方法是使用 bigint(8字节),对每个值进行数字移位并将结果进行或运算。您可以使用bit string operations 执行此操作,以使代码更容易——计算为位字符串,然后在存储之前转换为 bigint——或者如果您希望速度更好,只需手动乘/加。例如,以下是如何将两个字节一起存储到一个两字节结构中,然后再将它们取回:

int2 = 256 * byte1 + byte2
byte1 = int2 / 256
byte2 = int2 % 256

您可以将相同的想法扩展到以这种方式存储其中的 7 个。检索开销仍然很糟糕,但您实际上会在此过程中节省一些空间。但与行标题无关。

【讨论】:

  • 每一行有 6 x 1 字节整数 (?)、3 x 2 字节整数 (SMALLINT) 和 2 x 4 字节整数 (INT)。每行总共有 20 个字节,加上 Pg 的开销。由于 Pg 仅提供 SMALLINTs 作为最小值,因此我的 6 个 1 字节值将占用 12 个字节。我正在为这些寻找可能的替代方案。节省 6 个字节乘以 1200 亿行大约是 670 GB(如果我的计算正确的话)。也就是说,我确实需要单独取出这些值,所以我可能会为检索速度付出代价。我需要平衡这两者。在更大的方案中,0.7 TB 并不是一个巨大的空间。
【解决方案2】:

pg_catalog.char(另一种表示法 - “char”)类型只使用 1 个字节来存储其值。

select pg_column_size( 'A' );
pg_column_size
----------------
              2
(1 row)

select pg_column_size( 'A'::"char" );
pg_column_size
----------------
              1
(1 row)

【讨论】:

  • “char”类型只能“计数”从 0 到 127。试试这个:select i, ascii(i::"char") from generate_series(0, 127) s(i)。现在将 127 更改为 128。
  • @ClodoaldoNeto 不正确,它可以从-128 计数到 127,试试select i, ascii(i::"char") from generate_series(-128, 127) s(i)
【解决方案3】:

"char"

这是 PostgreSQL 中的单字节类型,范围为 -128,127。来自the docs,

"char" 类型(注意引号)与char(1) 的不同之处在于它只使用一个字节的存储空间。它在系统目录内部用作简单的枚举类型。

您可以将其偏向 [-128,127],方法是在写入数据库之前从 [0-255] 范围内的任何输入中减去 128,然后在从数据库中读取时将其添加回输出中。

-- works
SELECT (-128)::"char", 127::"char";

-- generates out of range
SELECT (-128)::"char";
SELECT 128::"char";

-- Shifts to unsigned range.
-- If you're going to be using "char"
-- review the results of this query!
SELECT
  x::int AS "inputUnsigned",
  chr(x) AS "extendedASCII",
  -- this is the "char" types representation for that input.
  signed::"char" AS "charRepresentation",

  signed     AS "inputUnsignedToSigned",
  signed+128 AS "inputUnsignedToSignedToUnsigned"
FROM generate_series(1,255) AS gs(x)
-- Here we map the input in the range of [0,255] to [-128,127]
CROSS JOIN LATERAL ( VALUES (x::int-128) )
  AS v(signed);

输出的小摘录

 inputUnsigned | extendedASCII | charRepresentation | inputUnsignedToSigned | inputUnsignedToSignedToUnsigned 
---------------+---------------+--------------------+-----------------------+---------------------------------
....
           190 | ¾             | >                  |                    62 |                             190
           191 | ¿             | ?                  |                    63 |                             191
           192 | À             | @                  |                    64 |                             192
           193 | Á             | A                  |                    65 |                             193
           194 | Â             | B                  |                    66 |                             194
           195 | Ã             | C                  |                    67 |                             195
           196 | Ä             | D                  |                    68 |                             196
...

我们使用generate_series(1,255) 只是因为chr(0) 抛出,因为您无法生成或输出ASCII NUL(PostgreSQL 使用cstrings)

pguint分机

Pguint 是提供两种单字节表示的扩展,

  • int1(签名)
  • uint1(未签名)

【讨论】:

    【解决方案4】:

    【讨论】:

    • 这显然是个坏建议。 Postgres bytea 的大小为 1 或 4 个字节加上实际的二进制字符串。所以在最好的情况下,你仍然需要为你的数据分配 2 个字节。
    【解决方案5】:

    您会使用这些值查找记录吗?

    如果是 - 使用普通数据类型,例如 int4(如果您使用 64 位架构,甚至可以使用 int8)。

    如果不是——首先问问自己——将这些值存储在 Pg 中的意义何在?您可以使用 bytea(复杂的 i/o)或位串(甚至更复杂的 i/o),但有什么意义呢?你将拥有多少亿条记录?您是否真的检查过较小的数据类型使用较少的空间(提示:它没有,检查一下 - 涉及数据对齐问题)?您是否认为较小的数据类型更快(事实并非如此。在 32 位架构上比较两个 int2 值实际上比比较两个 int4 值更复杂)。

    【讨论】:

    • 您有一些有效的问题,我可以通过提供更多信息来抢占先机。我有 6 个 8 位值,预计有 1200 亿行。因此,希望尽可能地节省空间。
    • 实际上它确实节省了存储 int2 与 int4 的空间。我在 8.4 上进行了测试,每个元组存储 7 x int2 = 38 个字节; 7 x char(1) = 41 字节; 7 x int4 = 52 字节。
    【解决方案6】:

    首先您询问了 7 个字节,但现在是 6 个字节。六个 8 位值与 MAC 地址大小和 PostgreSQL 的内置类型 macaddr 完全对应。您可以使用 MAC 语法 f.i. 插入这些字节。 A1-B2-C3-D4-E5-F6。

    【讨论】:

      【解决方案7】:

      我自己没有测试过它们,但有一些扩展;例如http://pgxn.org/dist/tinyint/ .

      【讨论】:

        猜你喜欢
        • 2014-02-13
        • 1970-01-01
        • 2016-12-13
        • 2018-01-13
        • 1970-01-01
        • 2013-10-23
        • 2014-04-30
        • 1970-01-01
        • 2022-08-04
        相关资源
        最近更新 更多