在 Postgres 中存储固定长度小数组的最佳数据模型(使用 SqlAlchemy)
Best data model to store small fixed-length array in Postgres (with SqlAlchemy)
我想存储一组每小时设定点。 24 个浮点数(0. 到 1.),将一天中的小时数映射到强度值。
我可以:
- 有 24 个列名为
_0
到 _23
。
(如何在 Sqlalchemy 中扩展 db.Model 以使用数字作为列名?)
- 有一个值 table
(id, value, hour_of_the_day)
。
- 使用postgres array type。 (但似乎我不能有固定长度的数组,我需要一些应用程序逻辑来 get/set 值。)
大部分值将一起查询并缩放(以便 0 到 1 的范围映射)自定义范围。
哪个型号比较好?
这在很大程度上取决于用例和要求。
1.
24 个单独的 float
列 ,可能为空(您的第一个选项)是存储大小(因此速度)的最佳选择。每个 float
8 个字节(默认为 float8
a.k.a。double precision
)或每个 real
(float4
)4 个字节并且没有开销。由于恰好有 24 个给定的列,因此您永远不会 运行 陷入设计问题,就像使用可变数字时那样。
SQL 中的合法标识符不能以数字开头。你必须总是双引号。容易出错且令人困惑。使用字符前缀,例如 h0
.. h23
.
2.
A 一对多 table,FK 约束为 id
而不是主要 table。你会让(id int, hour_of_the_day int, value float8)
,而不是(id, value, hour_of_the_day)
,没有space浪费在对齐填充上。添加 CHECK
约束以仅允许 hour_of_the_day
.
的值为 0 - 23
与 1 相比,这增加了存储大小。:每个单独的行占用大约 44 个字节。每行 28 字节的开销 + 16 字节的数据。您至少在 (id, hour_of_the_day)
上添加一个 PK 约束,它是使用 btree 索引实现的(与 tables 类似的页面和行结构)。这每行又增加了 36 个字节。所以我们最终每个值至少有 80 个字节(加上每个数据页的开销和 table)。 1..
的存储大小 的大约 10 倍
3.
数组? 不。存储比 1.(24 字节的数组开销)更昂贵,处理最不方便,强制完整性(最大/精确长度)并不那么简单。
更多优点和缺点
与2.
你需要连接两个tables来读取数据,这是更昂贵的。但也有各种优点。如果您经常需要读取或写入没有列 h0
- h23
的主 table,您会得到更便宜的。更新单个值可能更便宜,因为主要 table 保持不变。 OTOH,一次插入或更新相同 id
的所有 24 个值通常更昂贵(1 个新行版本与 24 个新行版本)。 2..
删除单个值的成本更低
The values will mostly be queried together ...
1..
便宜多了
... and scaled
使用 2 可能更简单。:将相同的操作应用于一列而不是 24 个不同的列。
等正如我所说,这在很大程度上取决于用例和要求。
相关:
- PostgreSQL: performance impact of extra columns
- Making sense of Postgres row sizes
- Is there any difference between integer and bit(n) data types for a bitmask?
我想存储一组每小时设定点。 24 个浮点数(0. 到 1.),将一天中的小时数映射到强度值。
我可以:
- 有 24 个列名为
_0
到_23
。 (如何在 Sqlalchemy 中扩展 db.Model 以使用数字作为列名?) - 有一个值 table
(id, value, hour_of_the_day)
。 - 使用postgres array type。 (但似乎我不能有固定长度的数组,我需要一些应用程序逻辑来 get/set 值。)
大部分值将一起查询并缩放(以便 0 到 1 的范围映射)自定义范围。
哪个型号比较好?
这在很大程度上取决于用例和要求。
1.
24 个单独的 float
列 ,可能为空(您的第一个选项)是存储大小(因此速度)的最佳选择。每个 float
8 个字节(默认为 float8
a.k.a。double precision
)或每个 real
(float4
)4 个字节并且没有开销。由于恰好有 24 个给定的列,因此您永远不会 运行 陷入设计问题,就像使用可变数字时那样。
SQL 中的合法标识符不能以数字开头。你必须总是双引号。容易出错且令人困惑。使用字符前缀,例如 h0
.. h23
.
2.
A 一对多 table,FK 约束为 id
而不是主要 table。你会让(id int, hour_of_the_day int, value float8)
,而不是(id, value, hour_of_the_day)
,没有space浪费在对齐填充上。添加 CHECK
约束以仅允许 hour_of_the_day
.
与 1 相比,这增加了存储大小。:每个单独的行占用大约 44 个字节。每行 28 字节的开销 + 16 字节的数据。您至少在 (id, hour_of_the_day)
上添加一个 PK 约束,它是使用 btree 索引实现的(与 tables 类似的页面和行结构)。这每行又增加了 36 个字节。所以我们最终每个值至少有 80 个字节(加上每个数据页的开销和 table)。 1..
3.
数组? 不。存储比 1.(24 字节的数组开销)更昂贵,处理最不方便,强制完整性(最大/精确长度)并不那么简单。
更多优点和缺点
与2.
你需要连接两个tables来读取数据,这是更昂贵的。但也有各种优点。如果您经常需要读取或写入没有列 h0
- h23
的主 table,您会得到更便宜的。更新单个值可能更便宜,因为主要 table 保持不变。 OTOH,一次插入或更新相同 id
的所有 24 个值通常更昂贵(1 个新行版本与 24 个新行版本)。 2..
The values will mostly be queried together ...
1..
便宜多了... and scaled
使用 2 可能更简单。:将相同的操作应用于一列而不是 24 个不同的列。
等正如我所说,这在很大程度上取决于用例和要求。
相关:
- PostgreSQL: performance impact of extra columns
- Making sense of Postgres row sizes
- Is there any difference between integer and bit(n) data types for a bitmask?