假设我有键/值/时间范围元组,例如:
CREATE TABLE historical_values(
key TEXT,
value NUMERIC,
from_time TIMESTAMPTZ,
to_time TIMESTAMPTZ
)
并且希望能够有效地查询特定键和时间的值(按降序排序),例如:
SELECT value
FROM historical_values
WHERE
key = [KEY]
AND from_time <= [TIME]
AND to_time >= [TIME]
ORDER BY value DESC
我应该使用哪种索引/类型来获得最佳查找性能?我怀疑我的解决方案会涉及tstzrange
和gist
索引,但我还是
不确定如何使用关键匹配和价值订购要求很好地发挥作用。
修改:以下是有关使用情况的更多信息。
理想情况下使用Postgres v9.6中提供的功能。
关系将包含约。每个键1k键和5m值。值是大整数(最多32个字节),大多数是唯一的。时间范围在几个小时到几年之间。时间跨度是5年。不允许NULL
值,但某些时间范围是开放式的(可以使用NULL
,也可以使用to_time
的未来时间。
主键是键和时间范围(因为每个键的时间范围只有一个历史值)。
常见操作是a)将to_time
更新为&#34;关闭&#34;历史值,以及b)使用from_time = NOW
插入新值。
可以查询所有值。分区是一种选择。
答案 0 :(得分:1)
对于像这样的大表(&#34; 1k键和每个键5m值&#34;)我建议优化存储,如:
CREATE TABLE hist_keys (
key_id serial PRIMARY KEY
, key text NOT NULL UNIQUE
);
CREATE TABLE hist_values (
hist_value_id bigserial PRIMARY KEY -- optional, see below!
, key_id int NOT NULL REFERENCES hist_keys
, value numeric
, from_time timestamptz NOT NULL
, to_time timestamptz NOT NULL
, CONSTRAINT range_valid CHECK (from_time <= to_time) -- or < ?
);
还有助于提高指数。
考虑分区。 key_id
上的列表分区。甚至可能在from_time
上添加子分区(此次分区)。 Read the manual here.
每个key_id
有一个分区,(并且constraint exclusion已启用!)Postgres只会查看给定密钥的小分区(和索引),而不是整个大表。主要胜利。
但我强烈建议首先升级到 Postgres 10 ,这会增加"declarative partitioning"。使管理分区变得更加容易。
更好的是,跳到 Postgres 11 (目前是测试版),这增加了分区的主要改进(包括性能改进)。最值得注意的是,为了您的目标获得最佳查找效果,引用chapter on partitioning in release notes for Postgres 11 (currently beta):
在查询处理期间允许更快的分区消除(Amit Langote,David Rowley,Dilip Kumar)
这可以加快对具有多个分区的分区表的访问速度。
在查询执行期间允许删除分区(David Rowley,Beena Emerson)
以前分区消除只能在计划时间进行, 意味着许多连接和准备好的查询无法使用分区消除。
从value
列的角度来看,所选行的小子集对于每个新查询都是任意的。我不希望您找到支持ORDER BY value DESC
索引的有用方法。我专注于其他专栏。如果您可以从中获取仅索引扫描(可能是btree和GiST),也许将value
添加为每个索引的最后一列。
不分区:
CREATE UNIQUE INDEX hist_btree_idx ON hist_values (key_id, from_time, to_time DESC);
UNIQUE
是可选的,但请参见下文
请注意反对from_time
和to_time
的排序顺序的重要性。见(密切相关!):
这几乎与在(key_id, from_time, to_time)
上实施PK的索引相同。不幸的是,我们不能将它用作PK索引。 Quoting the manual:
此外,它必须是具有默认排序顺序的b树索引。
因此,我在上面建议的表格设计中添加了bigserial
作为代理主键,并NOT NULL
约束加上UNIQUE
索引来强制执行您的唯一性规则。
在Postgres 10或更高版本中,请考虑使用IDENTITY
列:
在这种特殊情况下,您甚至可以使用PK约束来避免重复索引并使表保持最小大小。取决于完整的情况。您可能需要它用于FK约束或类似。参见:
像您已经怀疑的 GiST索引可能会更快。我建议保留表中的原始timestamptz
列(tstzrange
为16字节而不是32字节),并在安装附加模块key_id
后添加btree_gist
:
CREATE INDEX hist_gist_idx ON hist_values
USING GiST (key_id, tstzrange(from_time, to_time, '[]'));
表达式tstzrange(from_time, to_time, '[]')
构建范围 ,包括 上限和下限。 Read the manual here.
您的查询需要与索引匹配:
SELECT value
FROM hist_values
WHERE key = [KEY]
AND tstzrange(from_time, to_time, '[]') @> tstzrange([TIME_FROM], [TIME_TO], '[]')
ORDER BY value DESC;
它等同于你的原件
@>
being the range contains operator.
在key_id
使用每个key_id
的 单独表格,我们可以从索引中省略key_id
,从而改善尺寸和效果 - 尤其是对于GiST index - 我们也不需要额外的模块btree_gist
。结果在~1000个分区和相应的索引中:
CREATE INDEX hist999_gist_idx ON hist_values USING GiST (tstzrange(from_time, to_time, '[]'));
相关: