【问题标题】:Index to query sorted values in keyed time range在键控时间范围内查询排序值的索引
【发布时间】:2018-11-20 17:28:01
【问题描述】:

假设我有键/值/时间范围元组,例如:

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

我应该使用哪种索引/类型来获得最佳查找性能?我怀疑我的解决方案将涉及 tstzrangegist 索引,但我 不知道如何使其与键匹配和值排序要求相得益彰。

编辑:这里有更多关于使用的信息。

  • 理想情况下使用 Postgres v9.6 中可用的功能。

  • 关系将包含大约。每个键有 1k 个键和 5m 个值。值是大整数(最多 32 个字节),大多是唯一的。时间从几个小时到几年不等。时间跨度为 5 年。不允许使用NULL 值,但某些时间范围是开放式的(可以使用NULLto_time 的未来时间)。

  • 主键是键和时间范围(因为每个键只有一个时间范围的历史值)。

  • 常见操作是 a) 更新 to_time 以“关闭”历史值,以及 b) 使用 from_time = NOW 插入新值。

  • 可以查询所有值。分区是一种选择。

【问题讨论】:

  • Postgres 版本?基数?粗略的值频率? (多少个 dist. 键,每个键有多少个值?)查询中的典型时间范围(与总时间范围相比)?考虑tag info of [postgresql-performance] 中的说明。什么是PK?是否有 NULL 值?
  • 是的,当然。使用 Postgres v9.6。大约 1k 键。大约。每个键 5m 个值。值是大整数,大多是唯一的。时间从几个小时到几年不等。
  • edit问题:所有定义信息都应该在问题中。 “大整数” - 数据类型不能为integer(或bigint)的任何原因都会更有效。可以将键列替换为引用表history_keys 的整数 FK 列吗? (同样,更高效。)NOT NULL 约束/NULL 值呢?表是只读的吗?还是只添加后来的时间戳?旧行是只读的吗?分区是一种选择吗?是否可以选择升级到 pg 10(或 pg 11,目前是测试版)?是否有任何从不查询的大范围值?

标签: postgresql indexing database-design postgresql-performance


【解决方案1】:

数据库设计

对于这样的大表(“1k 个键和 5m 个值每个键”),我建议优化存储,例如:

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。我会专注于其他专栏。 也许添加value 作为每个索引的最后一列,如果您可以从中获得仅索引扫描(可能适用于 btree 和 GiST)。

不分区:

CREATE UNIQUE INDEX hist_btree_idx ON hist_values (key_id, from_time, to_time <b>DESC</b>);

UNIQUE 是可选的,但请参见下文。
请注意 from_timeto_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 个字节)并在安装附加模块 btree_gist 后添加 key_id

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;

相当于你的原版。
@&gt; being the range contains operator.

key_id上使用列表分区

每个key_id都有一个单独的表,我们可以从索引中省略key_id,从而提高大小和性能 - 特别是对于 GiST 索引 -然后我们也不需要额外的模块btree_gist。产生约 1000 个分区和相应的索引:

CREATE INDEX hist999_gist_idx ON hist_values USING GiST (tstzrange(from_time, to_time, '[]'));

相关:

【讨论】:

  • 注意:from_time timestamptz NOT NULL DEFAULT '-infinity'(与 to_time 类似)将简单地对时间间隔进行任何比较。
  • @joop:是的,这样可以避免 NULL 值出现意外。
  • @ErwinBrandstetter 感谢您的广泛回答。关于数据库设计,我的意思是 PK on (key, from_time, to_time),即每个键的任何时间段的一个历史值。很抱歉没有说得更清楚。除此之外,方法接缝非常整齐。我很好奇 a) 是什么让 PG 10 在分区上更快,以及我可能会考虑哪些因素改进?,b) 同样,如果我在 key 上使用 list-partitioning,这可能需要什么样的性能改进?谢谢!
  • @Benjamin:我删除了关于 PK 的过时部分,并在上面添加了更多内容,包括。为什么我希望分区有助于提高性能。不能给你确切的数字,你必须测试。但我预计会有很大的改进。
猜你喜欢
  • 2021-06-02
  • 1970-01-01
  • 2016-09-05
  • 2013-05-03
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多