用于在键控时间范围内查询已排序值的索引

时间:2018-06-11 12:47:00

标签: 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值,但某些时间范围是开放式的(可以使用NULL,也可以使用to_time的未来时间。

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

  • 常见操作是a)将to_time更新为&#34;关闭&#34;历史值,以及b)使用from_time = NOW插入新值。

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

1 个答案:

答案 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_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字节),并在安装附加模块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, '[]'));

相关: