约束排除以获取最新的 N 行

fff*_*abs 0 postgresql index partitioning

我正在管理一个包含几百万条记录的表,这些记录被实时插入。我的应用程序的一部分需要显示最后插入的 N 行,所以一开始我只是查询:

select id, logdate, content from measurements order by logdate DESC limit 500;
Run Code Online (Sandbox Code Playgroud)

几天后,我发现设置id为(对于此示例)更快10000000000 - extract(epoch from logdate),并将其用作PRIMARY KEY,所以

select id, date, content from measurements limit 500;
Run Code Online (Sandbox Code Playgroud)

自然会按 排序id,因此会产生最新的记录。

随着表的增长,它变得难以管理,所以我求助于分区。我按照文档说的那样做了

CREATE TABLE measurement_y2007m11 (
CHECK ( logdate >= DATE '2007-11-01' AND logdate < DATE '2007-12-01' )
) INHERITS (measurement);

CREATE TABLE measurement_y2007m12 (
CHECK ( logdate >= DATE '2007-12-01' AND logdate < DATE '2008-01-01' )
) INHERITS (measurement);

CREATE TABLE measurement_y2008m01 (
CHECK ( logdate >= DATE '2008-01-01' AND logdate < DATE '2008-02-01' )
) INHERITS (measurement);
Run Code Online (Sandbox Code Playgroud)

我将现有的行分布到它们各自的分区上。

问题是,我正在对 进行分区logdate,但是在查询时我不知道时间跨度。当我查询最后 500 行时,我不知道它们是否来自上周,上个季度的最后一个月。因此,查询计划器始终扫描所有分区。

我不敢相信以前没有人处理过同样的问题,这听起来微不足道,但让我感到困惑。

Erw*_*ter 5

误解一:“自然秩序”

<query without ORDER BY>
...自然会按 id 排序,因此会产生最新的记录。

没有在一个自然顺序SELECT发言。没有ORDER BY你以任意顺序获得行。通常,这将是 Postgres 可以满足您的查询的最便宜的顺序,即元组物理存储的顺序或在索引查找后检索它们的顺序。但没有任何保证。如果您的陈述似乎有效,这纯粹是运气/巧合,它随时可能中断。

请改用您的第一个查询。如果logdate实际上是 type date,或者如果您需要确定,您应该添加更多ORDER BY项目以打破联系并获得稳定的排序顺序。如果您不关心哪个,请附加您的(新)主键(见下文):

SELECT id, logdate, content
FROM   measurement
ORDER  BY logdate DESC, measurement_id DESC
LIMIT 500;
Run Code Online (Sandbox Code Playgroud)

如果保证最新的行(最大measurement-id)具有最新的logdate,您可以只ORDER BY measurement_id DESC,但不要认为这是理所当然的。在多用户环境中,具有较晚的行logdate可以比具有较早的另一行更快地写入logdate

这就是为什么您对新主键的想法不是很有用的原因之一:

10000000000 - extract(epoch from logdate),并将其用作 PRIMARY KEY

另一个原因:如果logdate不能保证是唯一的,它迟早会失败——它很可能不是。

改为使用serialmeasurement_id作为主键。或者,bigserial如果您期望随着时间的推移超过 2147483647 行。

指数

你声称你按照文档说的做了,文档说

对于每个分区,在键列上创建一个索引,以及...

再往下:

我们可能也需要键列上的索引:

CREATE INDEX measurement_y2006m02_logdate ON measurement_y2006m02 (logdate);
CREATE INDEX measurement_y2006m03_logdate ON measurement_y2006m03 (logdate);
...
Run Code Online (Sandbox Code Playgroud)

唯一的细微差别:手册中的示例使用更合理的单数形式作为表名:measurement而不是measurements.

如果你同意我的建议:

ORDER  BY logdate DESC, measurement_id DESC
Run Code Online (Sandbox Code Playgroud)

做到这一点:

CREATE INDEX measurement_y2006m02_logdate
ON measurement_y2006m02 (logdate DESC, measurement_id DESC);
...
Run Code Online (Sandbox Code Playgroud)

更多关于为什么这可能会有所帮助:

误解 2:“扫描所有分区”

因此,查询计划器始终扫描所有分区。

查询计划器将计划按顺序检查所有分区。但是只要查询得到满足(检索到 500 行),它就会停止执行。使用 进行测试EXPLAIN ANALYZE,您将看到(never executed)剩余分区后面的注释。

如果规划器不够聪明,无法从您的设置中得出最佳扫描顺序(现在无法测试),您可以帮助UNION ALL查询分区:

(
SELECT measurement_id, logdate, content FROM measurement_y2006m03_logdate
ORDER  BY logdate DESC, measurement_id DESC
)            -- parens needed to include ORDER BY in individual legs of UNION query
UNION ALL
(
SELECT measurement_id, logdate, content FROM measurement_y2006m02_logdate
ORDER  BY logdate DESC, measurement_id DESC
)
...          -- latest partition first

LIMIT 500;
Run Code Online (Sandbox Code Playgroud)

但这可能没有必要。