为啥 PostgreSQL 选择这个索引?
Posted
技术标签:
【中文标题】为啥 PostgreSQL 选择这个索引?【英文标题】:Why is PostgreSQL choosing this index?为什么 PostgreSQL 选择这个索引? 【发布时间】:2021-03-27 15:22:10 【问题描述】:前言 - 请在标记为重复之前,我意识到这个一般性问题已被多次询问和回答。我提出了一个非常具体的用例,无法在问题标题中合理地传达,并寻找特定于该案例的答案。恕我直言,请不要为投机性的答案而烦恼。我推测了很多,我正在寻找由 PostgreSQL 查询计划器的特定知识或文档支持的明确答案。
鉴于此表和此查询,
CREATE TABLE grouptest
(
id1 int, id2 int, g1 int, g2 int, g3 int, t timestamp
);
CREATE INDEX idx_g2 ON grouptest (id1, id2, g1, g2, t);
CREATE INDEX idx_g3 ON grouptest (id1, id2, g2, g3, t);
EXPLAIN
SELECT g2
FROM grouptest
WHERE id1 = 123 AND id2 = 234 AND g1 = 1234;
对我来说,使用最佳索引的明显选择是第一个索引idx_g2
,因为它与正在查询的内容完全匹配。但是,查询规划器选择了第二个索引idx_g3
。
Index Scan using idx_g3 on grouptest (cost=0.15..8.18 rows=1 width=4)
Index Cond: ((id1 = 123) AND (id2 = 234) AND (t >= '2021-03-01 00:00:00'::timestamp without time zone) AND (t < '2021-03-02 00:00:00'::timestamp without time zone))
Filter: (g1 = 1234)
除了不是显而易见的选择之外,它甚至必须转到表数据以获取 g1
进行过滤,而它本可以对第一个索引进行仅索引扫描。这是完全一致的。它每次都会选择它,不管我discard plans
,在两者之间运行其他使用第一个索引的查询等等。我已经在其他几个具有相同一般模式的更复杂的表/查询上尝试过这个,并得到相同的结果。我展示的示例只是将用例简化为最基本的形式。
但令人震惊的是!如果我以相反的顺序创建索引,
CREATE INDEX idx_g3 ON grouptest (id1, id2, g2, g3, t);
CREATE INDEX idx_g2 ON grouptest (id1, id2, g1, g2, t);
它会选择我认为最好的索引idx_g2
,并且正如我所期望的那样,它可以进行仅索引扫描。
Index Only Scan using idx_g2 on grouptest (cost=0.15..8.18 rows=1 width=4)
Index Cond: ((id1 = 123) AND (id2 = 234) AND (g1 = 1234) AND (t >= '2021-03-01 00:00:00'::timestamp without time zone) AND (t < '2021-03-02 00:00:00'::timestamp without time zone))
当然,此时表中没有数据,因此没有统计数据会将其推向一个方向而不是另一个方向。我敢肯定,有些人会说“相信查询规划器,它会做出最好的选择”。这可能是真的,一旦我的表中有数百万行,也许它会做出另一个选择,我还没有测试过。
但它困扰我的原因是,当我第一次设计我的表和索引时,在我有机会加载包含大量测试数据的表之前,我通常会运行这样的示例查询来验证我'已经创建了良好且有用的索引。如果我发现它没有使用我期望的索引,我会开始寻找我做错了什么。
如果它真的只是没有数据和统计信息的问题,所以它只是任意选择(但显然以某种一致的方式,比如更喜欢最新创建的),是否有任何临时查询计划程序选项会强制它再努力一点?我查看了选项,没有看到任何我认为有用的东西。
我在 AWS RDB 中的 PostgreSQL v11.10 上运行
提前致谢:)
【问题讨论】:
表中没有行(尽管解释计划表明有行)。使用哪个索引并不重要。我很惊讶它完全使用索引。不要从琐碎的小表中概括索引的使用。 @GordonLinoff,如果这真的只是表中没有数据的问题,我也很惊讶它会费心使用索引而不是仅仅使用 seq 扫描。我将不得不等待并使用大量数据进行测试。但正如我所说,希望能够合理地验证我有良好的索引,规划器可以将其用于特定查询而无需填充表。 。 .因为表中没有数据,它可能会识别出统计信息都是最新的,所以它读取索引而不是表,因为它“更小”。并不是真的更小,还有一个I/O的数据页。但这是一个边缘案例。我不确定是否有可以欺骗表大小的工具,以查看优化器在更大数据上生成的计划。 如果有一个可用的工具可以推送统计数据来模拟不同的数据大小和模式,看看它会做出什么选择,那就太好了。我可能想多了——应该相信我的索引选择和查询规划器,直到事情开始出现问题:) 计划器是由数据驱动的统计驱动的。没有数据,没有统计数据,也没有真正的计划者。此外,索引仅与它所索引的数据一样有用。如果索引不在最常用的数据上,或者数据项不够独特,那么它不会有太大帮助。为了得到你想要的,你需要用测试数据填充表并运行适当的测试查询,最好使用EXPLAIN ANALYZE
。
【参考方案1】:
我想到目前为止的cmets已经总结出来了,谢谢。
这只是没有数据的结果,所以没有统计数据,可能只是一个随意的选择。
一旦我用数据验证,我会更新并可能相应地更改标题。
【讨论】:
以上是关于为啥 PostgreSQL 选择这个索引?的主要内容,如果未能解决你的问题,请参考以下文章
为啥这个查询这么慢? - PostgreSQL - 从 SERIAL、TIMESTAMP 和 NUMERIC(6,2) 中选择