PostgreSQL未在筛选的多排序查询上使用索引
我有一张非常简单的桌子PostgreSQL未在筛选的多排序查询上使用索引,sql,postgresql,sorting,indexing,postgresql-performance,Sql,Postgresql,Sorting,Indexing,Postgresql Performance,我有一张非常简单的桌子 CREATE TABLE approved_posts ( project_id INTEGER, feed_id INTEGER, post_id INTEGER, approved_time TIMESTAMP NOT NULL, post_time TIMESTAMP NOT NULL, PRIMARY KEY (project_id, feed_id, post_id) ) 我正在尝试优化此查询: SELECT * FROM approv
CREATE TABLE approved_posts (
project_id INTEGER,
feed_id INTEGER,
post_id INTEGER,
approved_time TIMESTAMP NOT NULL,
post_time TIMESTAMP NOT NULL,
PRIMARY KEY (project_id, feed_id, post_id)
)
我正在尝试优化此查询:
SELECT *
FROM approved_posts
WHERE feed_id IN (?, ?, ?)
AND project_id = ?
ORDER BY approved_time DESC, post_time DESC
LIMIT 1;
查询优化器将获取与谓词匹配的每一个approved\u post
,对所有100k个结果进行排序,并返回找到的前一个结果
我确实有一个关于项目id、提要id、批准时间、发布时间的索引,如果我:
A.按post\u时间删除排序
,或
B.用单个=?
替换(?,?)
中的
然后,它只需进行反向索引扫描即可获得第一个结果,并且速度极快
选项A:
Limit (cost=0.43..6.57 rows=1 width=24) (actual time=0.101..0.101 rows=1 loops=1)
-> Index Scan Backward using approved_posts_approved_time_idx on approved_posts p (cost=0.43..840483.02 rows=136940 width=24) (actual time=0.100..0.100 rows=1 loops=1)
Filter: (feed_id = ANY ('{73321,73771,73772,73773,73774}'::integer[]))
Rows Removed by Filter: 37
Total runtime: 0.129 ms
Limit (cost=0.43..3.31 rows=1 width=24) (actual time=0.065..0.065 rows=1 loops=1)
-> Index Scan Backward using approved_posts_full_pagination_index on approved_posts p (cost=0.43..126884.70 rows=44049 width=24) (actual time=0.063..0.063 rows=1 loops=1)
Index Cond: ((project_id = 148772) AND (feed_id = 73321))
Total runtime: 0.092 ms
选项B:
Limit (cost=0.43..6.57 rows=1 width=24) (actual time=0.101..0.101 rows=1 loops=1)
-> Index Scan Backward using approved_posts_approved_time_idx on approved_posts p (cost=0.43..840483.02 rows=136940 width=24) (actual time=0.100..0.100 rows=1 loops=1)
Filter: (feed_id = ANY ('{73321,73771,73772,73773,73774}'::integer[]))
Rows Removed by Filter: 37
Total runtime: 0.129 ms
Limit (cost=0.43..3.31 rows=1 width=24) (actual time=0.065..0.065 rows=1 loops=1)
-> Index Scan Backward using approved_posts_full_pagination_index on approved_posts p (cost=0.43..126884.70 rows=44049 width=24) (actual time=0.063..0.063 rows=1 loops=1)
Index Cond: ((project_id = 148772) AND (feed_id = 73321))
Total runtime: 0.092 ms
但是如果没有这些调整,它的性能就不那么好
Limit (cost=169792.16..169792.17 rows=1 width=24) (actual time=510.225..510.225 rows=1 loops=1)
-> Sort (cost=169792.16..170118.06 rows=130357 width=24) (actual time=510.224..510.224 rows=1 loops=1)
Sort Key: approved_time, post_time
Sort Method: top-N heapsort Memory: 25kB
-> Bitmap Heap Scan on approved_posts p (cost=12324.41..169140.38 rows=130357 width=24) (actual time=362.210..469.387 rows=126260 loops=1)
Recheck Cond: (feed_id = ANY ('{73321,73771,73772,73773,73774}'::integer[]))
-> Bitmap Index Scan on approved_posts_feed_id_idx (cost=0.00..12291.82 rows=130357 width=0) (actual time=354.496..354.496 rows=126260 loops=1)
Index Cond: (feed_id = ANY ('{73321,73771,73772,73773,73774}'::integer[]))
Total runtime: 510.265 ms
我甚至可以在这5个提要ID上添加一个条件索引,它将再次做正确的事情
我目前最好的解决方案是将每个feed\u id
放在自己的查询中,并在它们之间进行大规模的联合。但这并不能很好地扩展,因为我可能想从30个提要中选择前500个提要,拉入15k行并对它们进行排序。使用此策略管理补偿也有点复杂
有人知道我如何在
子句中对索引良好的数据进行两种排序,并让Postgres做正确的事情吗
我正在使用Postgres9.3.3。以下是我的索引:
"approved_posts_project_id_feed_id_post_id_key" UNIQUE CONSTRAINT, btree (project_id, feed_id, post_id)
"approved_posts_approved_time_idx" btree (approved_time)
"approved_posts_feed_id_idx" btree (feed_id)
"approved_posts_full_pagination_index" btree (project_id, feed_id, approved_time, post_time)
"approved_posts_post_id_idx" btree (post_id)
"approved_posts_post_time_idx" btree (post_time)
"approved_posts_project_id_idx" btree (project_id)
-> Index Scan Backward using approved_posts_approved_time_idx
on approved_posts p (cost=0.43..840483.02 rows=136940 width=24)
(actual time=0.100..0.100 rows=1 loops=1)
Filter: (feed_id = ANY ('{73321,73771,73772,73773,73774}'::integer[]))
"approved_posts_foo_idx" btree (project_id, approved_time DESC, post_time DESC)
所有列都不能为空
此表有2m行,分为200个提要ID和19个项目ID
以下是最常见的提要ID:
feed_id | count
---------+--------
73607 | 558860
73837 | 354018
73832 | 220285
73836 | 172664
73321 | 118695
73819 | 95999
73821 | 75871
73056 | 65779
73070 | 54655
73827 | 43710
73079 | 36700
73574 | 36111
73055 | 25682
73072 | 22596
73589 | 19856
73953 | 15286
73159 | 13059
73839 | 8925
就每个feedid
/projectid
配对的最小/最大/平均基数而言,我们有:
min | max | avg
-----+--------+-----------------------
1 | 559021 | 9427.9140271493212670
据我所知,如果第一个“where”不是密钥的第一部分,则不会使用密钥。尝试将查询中“where's”的顺序切换为project\u id和feed\u id。对于feed\u id
,Postgres很难找到最佳的查询计划。每个feed\u id
可以与1-559021行关联(根据您的数字)。Postgres目前还不够聪明,无法单独看到LIMIT 1
特例的潜在优化。一个由多个查询组成的UNION ALL
(不仅仅是UNION
),每个查询都有一个feed\u id
和LIMIT 1
,再加上另一个外部LIMIT 1
(就像您似乎已经尝试过的那样)展示了这一潜力,但需要复杂的查询串联以获得数量可变的输入值
还有另一种方法可以说服查询计划员,它可以使用索引扫描从索引中为每个提要id
选择第一行:使用横向连接重写查询:
SELECT a.*
FROM (VALUES (?), (?), (?)) AS t(feed_id)
, LATERAL (
SELECT *
FROM approved_posts
WHERE project_id = ?
AND feed_id = t.feed_id
ORDER BY approved_time DESC, post_time DESC
LIMIT 1
) a
ORDER BY approved_time DESC, post_time DESC
LIMIT 1;
或者,对于feed\u id
的可变数量的值更方便:
SELECT a.*
FROM unnest(?) AS t(feed_id) -- provide int[] var
, LATERAL ( ...
为变量传递一个整数数组,如“{123,234,345}”::int[]
。这也可以通过使用变量参数的函数优雅地实现。然后,您可以传递整数
值列表:
你在(project\u id,feed\u id,approved\u time,post\u time)上的索引可以实现这一点,因为Postgres可以将索引向后扫描几乎和向前扫描一样快,但是(project\u id,feed\u id,approved\u time DESC,post\u time DESC)
会更好。见:
如果不需要返回表中的所有列,甚至可以选择仅索引扫描
您的列approved\u time
,post\u time
已定义非空
。否则,您必须做更多的工作:
详细说明横向连接技术的相关答案:
你的选择A为什么奏效?
仔细观察会发现两件事:
"approved_posts_project_id_feed_id_post_id_key" UNIQUE CONSTRAINT, btree (project_id, feed_id, post_id)
"approved_posts_approved_time_idx" btree (approved_time)
"approved_posts_feed_id_idx" btree (feed_id)
"approved_posts_full_pagination_index" btree (project_id, feed_id, approved_time, post_time)
"approved_posts_post_id_idx" btree (post_id)
"approved_posts_post_time_idx" btree (post_time)
"approved_posts_project_id_idx" btree (project_id)
-> Index Scan Backward using approved_posts_approved_time_idx
on approved_posts p (cost=0.43..840483.02 rows=136940 width=24)
(actual time=0.100..0.100 rows=1 loops=1)
Filter: (feed_id = ANY ('{73321,73771,73772,73773,73774}'::integer[]))
"approved_posts_foo_idx" btree (project_id, approved_time DESC, post_time DESC)
有选择地增加列project\u id
和feed\u id
的统计目标可能会有好处,因此可以更准确地估计两种策略之间的转折点
由于您的项目只有旧的行(),因此可以通过提示每个项目(和/或每个提要id
)的批准的\u时间的最大值来改进此查询,或者至少是一个上限
SELECT ...
WHERE ...
AND approved_time <= $upper_bound
选择。。。
哪里
时间还没到呢!谢谢你。我一直在将DESCs交换到ASCs和posttime/approvedtime,但没有想到交换WHERE条件。绝对值得一试<代码>9.3.3
回避了一个问题:为什么不至少9.3.9(如果9.4不是一个选项)?。我们将根据您的建议进行升级。您提供了所有必要的详细信息,这使我能够找到您感兴趣问题的答案。许多问题无法提供基本信息,这在这里是一个经常令人讨厌的问题,现在让你的问题在这方面大放异彩。这是目前教postgres使用哪个索引的最优雅的方式,在我们的查询生成器中更容易适应!很高兴知道,巨大的值范围是postgres出错的地方。今天早上我遇到了一个问题:如果根本的问题是每个提要id只有1个条目,那么为什么我们放弃二次排序(并且只按批准的时间描述排序)它选择反向索引扫描而不需要任何其他更改?编辑:事实上,想想看,因为按批准的时间排序只会对(批准的时间、发布的时间)进行反向索引扫描,所以它实际上已经以批准的时间描述、发布的时间描述顺序返回数据。一旦我们简单地按照博士后已经给我们的顺序要求它,为什么世界上的博士后会改变它的计划?@MikeFairhurst:好问题,我对这个转折点感到困惑