在键控时间范围内查询排序值的索引
Index to query sorted values in keyed time range
假设我有 key/value/timerange 个元组,例如:
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
我应该使用哪种 index/types 来获得最佳查找性能?我怀疑我的解决方案将涉及 tstzrange
和 gist
索引,但我
不确定如何让它很好地满足键匹配和值排序要求。
编辑:这里有一些关于用法的更多信息。
最好使用 Postgres v9.6 中提供的功能。
关系将包含大约。 1k 个键和每个键 5m 个值。值是大整数(最多 32 个字节),大部分是唯一的。时间从几个小时到几年不等。时间跨度为 5 年。不允许 NULL
值,但某些时间范围是开放式的(可以使用 NULL
或 to_time
的遥远未来的时间)。
主键是键和时间范围(因为对于一个时间范围,每个键只有一个历史值)。
常见操作是 a) 将 to_time
更新为 "close" 历史值,以及 b) 使用 from_time = NOW
.
插入新值
所有值均可查询。分区是一种选择。
数据库设计
对于像这样的大 table(“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 只会查看给定键的小分区(和索引),而不是整个大分区 table.重大胜利。
但我强烈建议首先升级到至少 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
与索引。我会专注于其他专栏。 也许 添加 value
作为每个索引的最后一列,如果你可以从中获得仅索引扫描(可能用于 btree 和 GiST)。
不分区:
CREATE UNIQUE INDEX hist_btree_idx ON hist_values (key_id, from_time, to_time <b>DESC</b>);
UNIQUE
是可选的,但请参阅下文。
请注意 from_time
和 to_time
的相反排序顺序的重要性。参见(密切相关!):
这与在 (key_id, from_time, to_time)
上实现 PK 的索引几乎相同。遗憾的是,我们不能将其用作 PK 指标。 Quoting the manual:
Also, it must be a b-tree index with default sort ordering.
所以我在上面建议的 table 设计中添加了一个 bigserial
作为代理主键,并添加了 NOT NULL
约束和 UNIQUE
索引来强制执行唯一性规则。
在 Postgres 10 或更高版本中考虑 IDENTITY
列:
- Auto increment table column
在这种特殊情况下,您甚至可以使用 PK 约束来避免重复索引并将 table 保持在最小大小。取决于完整的情况。 FK 约束或类似约束可能需要它。参见:
- How does PostgreSQL enforce the UNIQUE constraint / what type of index does it use?
A GiST 索引 就像您已经怀疑的那样可能更快。我建议将您原来的 timestamptz
列保留在 table 中(16 字节而不是 tstzrange
的 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
有一个 单独的 table,我们可以从中省略 key_id
索引,提高大小和性能 - 特别是对于 GiST 索引 - 我们也不需要额外的模块 btree_gist
。得到约 1000 个分区和相应的索引:
CREATE INDEX hist999_gist_idx ON hist_values USING GiST (tstzrange(from_time, to_time, '[]'));
相关:
- Store the day of the week and time?
假设我有 key/value/timerange 个元组,例如:
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
我应该使用哪种 index/types 来获得最佳查找性能?我怀疑我的解决方案将涉及 tstzrange
和 gist
索引,但我
不确定如何让它很好地满足键匹配和值排序要求。
编辑:这里有一些关于用法的更多信息。
最好使用 Postgres v9.6 中提供的功能。
关系将包含大约。 1k 个键和每个键 5m 个值。值是大整数(最多 32 个字节),大部分是唯一的。时间从几个小时到几年不等。时间跨度为 5 年。不允许
NULL
值,但某些时间范围是开放式的(可以使用NULL
或to_time
的遥远未来的时间)。主键是键和时间范围(因为对于一个时间范围,每个键只有一个历史值)。
常见操作是 a) 将
插入新值to_time
更新为 "close" 历史值,以及 b) 使用from_time = NOW
.所有值均可查询。分区是一种选择。
数据库设计
对于像这样的大 table(“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 只会查看给定键的小分区(和索引),而不是整个大分区 table.重大胜利。
但我强烈建议首先升级到至少 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
与索引。我会专注于其他专栏。 也许 添加 value
作为每个索引的最后一列,如果你可以从中获得仅索引扫描(可能用于 btree 和 GiST)。
不分区:
CREATE UNIQUE INDEX hist_btree_idx ON hist_values (key_id, from_time, to_time <b>DESC</b>);
UNIQUE
是可选的,但请参阅下文。
请注意 from_time
和 to_time
的相反排序顺序的重要性。参见(密切相关!):
这与在 (key_id, from_time, to_time)
上实现 PK 的索引几乎相同。遗憾的是,我们不能将其用作 PK 指标。 Quoting the manual:
Also, it must be a b-tree index with default sort ordering.
所以我在上面建议的 table 设计中添加了一个 bigserial
作为代理主键,并添加了 NOT NULL
约束和 UNIQUE
索引来强制执行唯一性规则。
在 Postgres 10 或更高版本中考虑 IDENTITY
列:
- Auto increment table column
在这种特殊情况下,您甚至可以使用 PK 约束来避免重复索引并将 table 保持在最小大小。取决于完整的情况。 FK 约束或类似约束可能需要它。参见:
- How does PostgreSQL enforce the UNIQUE constraint / what type of index does it use?
A GiST 索引 就像您已经怀疑的那样可能更快。我建议将您原来的 timestamptz
列保留在 table 中(16 字节而不是 tstzrange
的 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
有一个 单独的 table,我们可以从中省略 key_id
索引,提高大小和性能 - 特别是对于 GiST 索引 - 我们也不需要额外的模块 btree_gist
。得到约 1000 个分区和相应的索引:
CREATE INDEX hist999_gist_idx ON hist_values USING GiST (tstzrange(from_time, to_time, '[]'));
相关:
- Store the day of the week and time?