postgresql - 在键控时间范围内查询排序值的索引

标签 postgresql indexing database-design postgresql-performance

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

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 的 future 时间)。

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

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

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

最佳答案

数据库设计

对于这样的大表(“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) :

  • Allow faster partition elimination during query processing (Amit Langote, David Rowley, Dilip Kumar)

    This speeds access to partitioned tables with many partitions.

  • Allow partition elimination during query execution (David Rowley, Beena Emerson)

    Previously partition elimination could only happen at planning time, meaning many joins and prepared queries could not use partition elimination.

索引

value 列的角度来看,所选行的小子集对于每个新查询都是任意的。我不希望您会找到一种有用的方法来支持带有索引的 ORDER BY value DESC。我会专注于其他专栏。 也许如果您可以从中获取仅索引扫描(可能用于 btree 和 GiST),则将 value 添加为每个索引的最后一列。

不分区:

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:

Also, it must be a b-tree index with default sort ordering.

因此,我在上面建议的表设计中添加了一个 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 索引 - 我们也不需要额外的模块 btree_gist。产生约 1000 个分区和相应的索引:

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

相关:

关于postgresql - 在键控时间范围内查询排序值的索引,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/50798132/

相关文章:

sql - 如何通过比较 A 列中具有相同值的记录中 B、C 列中的值来选择记录?

database - 我应该什么时候使用 Datomic?

postgresql - 如何防止从 SPA 创建对象时重复 id?还是我应该留在分贝?

django - 异常值 : column home_profile. 目标不存在

r - 如何显示 NA 的索引?

postgresql - 为什么 Postgres 不在简单的 GROUP BY 上使用索引?

database-design - ER 建模问题

.net - 是否需要连续编号?

Postgresql优化问题

javascript - 在存在多个 div 的特定 div 中查找项目的索引