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 行时,我不知道它们是否来自上周,上个季度的最后一个月。因此,查询计划器始终扫描所有分区。
我不敢相信以前没有人处理过同样的问题,这听起来微不足道,但让我感到困惑。
<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
不能保证是唯一的,它迟早会失败——它很可能不是。
改为使用serial
列measurement_id
作为主键。或者,bigserial
如果您期望随着时间的推移超过 2147483647 行。
你声称你按照文档说的做了,文档说:
对于每个分区,在键列上创建一个索引,以及...
再往下:
我们可能也需要键列上的索引:
Run Code Online (Sandbox Code Playgroud)CREATE INDEX measurement_y2006m02_logdate ON measurement_y2006m02 (logdate); CREATE INDEX measurement_y2006m03_logdate ON measurement_y2006m03 (logdate); ...
唯一的细微差别:手册中的示例使用更合理的单数形式作为表名: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)
更多关于为什么这可能会有所帮助:
因此,查询计划器始终扫描所有分区。
查询计划器将计划按顺序检查所有分区。但是只要查询得到满足(检索到 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)
但这可能没有必要。