【问题标题】:PostgreSQL query not using index in productionPostgreSQL 查询未在生产中使用索引
【发布时间】:2012-03-17 13:27:56
【问题描述】:

我注意到一些奇怪/奇怪的事情:

开发/生产中完全相同的查询未使用相同的查询路径。特别是,开发版本正在使用生产中省略的索引(有利于 seqscan)。

唯一真正的区别是生产中的数据集要大得多——索引大小为 1034 MB,而生产中的数据集为 29 MB。如果索引(或表)太大,PostgreSQL 会放弃使用索引吗?

编辑:EXPLAIN ANALYZE 用于两个查询:

发展:

Limit  (cost=41638.15..41638.20 rows=20 width=154) (actual time=159.576..159.581 rows=20 loops=1)
  ->  Sort  (cost=41638.15..41675.10 rows=14779 width=154) (actual time=159.575..159.577 rows=20 loops=1)
        Sort Key: (sum(scenario_ad_group_performances.clicks))
        Sort Method: top-N heapsort  Memory: 35kB
        ->  GroupAggregate  (cost=0.00..41244.89 rows=14779 width=154) (actual time=0.040..151.535 rows=14197 loops=1)
              ->  Nested Loop Left Join  (cost=0.00..31843.75 rows=93800 width=154) (actual time=0.022..82.509 rows=50059 loops=1)
                    ->  Merge Left Join  (cost=0.00..4203.46 rows=14779 width=118) (actual time=0.017..27.103 rows=14197 loops=1)
                          Merge Cond: (scenario_ad_groups.id = scenario_ad_group_vendor_instances.ad_group_id)
                          ->  Index Scan using scenario_ad_groups_pkey on scenario_ad_groups  (cost=0.00..2227.06 rows=14779 width=114) (actual time=0.009..12.085 rows=14197 loops=1)
                                Filter: (scenario_id = 22)
                          ->  Index Scan using index_scenario_ad_group_vendor_instances_on_ad_group_id on scenario_ad_group_vendor_instances  (cost=0.00..1737.02 rows=27447 width=8) (actual time=0.007..7.021 rows=16528 loops=1)
                                Filter: (ad_vendor_id = ANY ('{1,2,3}'::integer[]))
                    ->  Index Scan using index_ad_group_performances_on_vendor_instance_id_and_date on scenario_ad_group_performances  (cost=0.00..1.73 rows=11 width=44) (actual time=0.002..0.003 rows=3 loops=14197)
                          Index Cond: ((vendor_instance_id = scenario_ad_group_vendor_instances.id) AND (date >= '2012-02-01'::date) AND (date <= '2012-02-28'::date))
Total runtime: 159.710 ms

生产:

Limit  (cost=822401.35..822401.40 rows=20 width=179) (actual time=21279.547..21279.591 rows=20 loops=1)
  ->  Sort  (cost=822401.35..822488.42 rows=34828 width=179) (actual time=21279.543..21279.560 rows=20 loops=1)
        Sort Key: (sum(scenario_ad_group_performances.clicks))
        Sort Method: top-N heapsort  Memory: 33kB
        ->  GroupAggregate  (cost=775502.60..821474.59 rows=34828 width=179) (actual time=19126.783..21226.772 rows=34495 loops=1)
              ->  Sort  (cost=775502.60..776739.48 rows=494751 width=179) (actual time=19125.902..19884.164 rows=675253 loops=1)
                    Sort Key: scenario_ad_groups.id
                    Sort Method: external merge  Disk: 94200kB
                    ->  Hash Right Join  (cost=25743.86..596796.70 rows=494751 width=179) (actual time=1155.491..16720.460 rows=675253 loops=1)
                          Hash Cond: (scenario_ad_group_performances.vendor_instance_id = scenario_ad_group_vendor_instances.id)
                          ->  Seq Scan on scenario_ad_group_performances  (cost=0.00..476354.29 rows=4158678 width=44) (actual time=0.043..8949.640 rows=4307019 loops=1)
                                Filter: ((date >= '2012-02-01'::date) AND (date <= '2012-02-28'::date))
                          ->  Hash  (cost=24047.72..24047.72 rows=51371 width=143) (actual time=1123.896..1123.896 rows=34495 loops=1)
                                Buckets: 1024  Batches: 16  Memory Usage: 392kB
                                ->  Hash Right Join  (cost=6625.90..24047.72 rows=51371 width=143) (actual time=92.257..1070.786 rows=34495 loops=1)
                                      Hash Cond: (scenario_ad_group_vendor_instances.ad_group_id = scenario_ad_groups.id)
                                      ->  Seq Scan on scenario_ad_group_vendor_instances  (cost=0.00..11336.31 rows=317174 width=8) (actual time=0.020..451.496 rows=431770 loops=1)
                                            Filter: (ad_vendor_id = ANY ('{1,2,3}'::integer[]))
                                      ->  Hash  (cost=5475.55..5475.55 rows=34828 width=139) (actual time=88.311..88.311 rows=34495 loops=1)
                                            Buckets: 1024  Batches: 8  Memory Usage: 726kB
                                            ->  Bitmap Heap Scan on scenario_ad_groups  (cost=798.20..5475.55 rows=34828 width=139) (actual time=4.451..44.065 rows=34495 loops=1)
                                                  Recheck Cond: (scenario_id = 276)
                                                  ->  Bitmap Index Scan on index_scenario_ad_groups_on_scenario_id  (cost=0.00..789.49 rows=34828 width=0) (actual time=4.232..4.232 rows=37006 loops=1)
                                                        Index Cond: (scenario_id = 276)
Total runtime: 21306.697 ms

【问题讨论】:

  • 您的生产环境中的索引可能选择性不够
  • “选择性不够”是什么意思?
  • 如果您有一个索引布尔列,其中 99% 的数据是 false,则扫描索引然后扫描 99% 的表没有太大意义,而不是扫描整个表桌子。对于 false 值,该索引的选择性不够。

标签: sql database postgresql indexing


【解决方案1】:

免责声明

我很少使用 PostgreSQL。我是根据我对 SQL Server 索引使用和执行计划的了解来回答的。如果我有什么地方出错了,我请求 PostgreSQL 大神们宽恕。

查询优化器是动态的

您说您的查询计划已从开发环境更改为生产环境。这是可以预料的。查询优化器旨在根据当前数据条件生成最佳执行计划。在不同的条件下,优化器可能会决定使用表扫描比使用索引扫描更有效。

什么时候使用表扫描比使用索引扫描更有效?

SELECT A, B
FROM someTable
WHERE A = 'SOME VALUE'

假设您在列 A 上有一个非聚集索引。在这种情况下,您正在过滤列 A,这可能会利用索引。如果索引具有足够的选择性,这将是有效的——基本上,索引有多少不同的值?数据库保留有关此选择性信息的统计信息,并在计算执行计划的成本时使用这些统计信息。

如果您的表中有 100 万行,但 A 只有 10 个可能的值,那么您的查询可能会返回大约 100K 行。因为索引是非聚集索引,并且您返回的列未包含在索引中,B,所以需要对返回的每一行执行查找。这些查找是随机访问查找,比表扫描使用的顺序读取要昂贵得多。在某一点上,数据库只执行表扫描而不是索引扫描会变得更有效。

这只是一种情况,还有很多其他情况。如果不了解更多关于您的数据是什么样的、您的索引是什么样的以及您如何尝试访问数据,就很难知道。

回答原来的问题

如果索引(或表)太大,PostgreSQL 会放弃使用索引吗?不会。更有可能的是,在您访问数据的方式上,PostgreSQL 使用索引与使用表扫描相比效率较低。

PostgreSQL 常见问题解答涉及这个主题(请参阅:为什么我的查询很慢?为什么他们不使用我的索引?):https://wiki.postgresql.org/wiki/FAQ#Why_are_my_queries_slow.3F_Why_don.27t_they_use_my_indexes.3F

【讨论】:

  • 维基页面很棒。
  • 最后一个链接坏了 - 工作链接:wiki.postgresql.org/wiki/…
  • 非常大胆地用这种免责声明开始回答!
【解决方案2】:

Postgres 的查询优化器提出了多种方案(例如索引与 seq-scan),并使用有关您的表的统计信息以及配置中设置的磁盘/内存/索引/表访问的相对成本来评估它们。

您是否使用了EXPLAIN 命令来查看为什么省略了索引使用?您是否使用EXPLAIN ANALYZE 查明该决定是否有误?我们可以看看输出吗?

编辑:

就像在不同系统上分析两个不同的奇异查询一样困难,我想我看到了几件事。

生产环境中每个成本单位的实际/成本率约为 20-100 毫秒。我什至不是 DBA,但这似乎是一致的。开发环境有261个主查询。这看起来对吗?您是否期望生产环境的原始速度(内存/磁盘/CPU)比开发环境快 2-10 倍?

由于生产环境有一个复杂的查询计划,看起来它正在完成它的工作。毫无疑问,开发环境的计划和更多已被考虑,并且被认为成本太高。在我的经验中,20-100 的差异并没有那么大(但同样,不是 DBA),并且表明没有任何偏离标准的地方。不过,您可能希望在 DB 上运行 VACUUM 以防万一。

我没有足够的经验和耐心来解码完整的查询,但是否有一个非规范化/NOSQL 化点进行优化?

最大的瓶颈似乎是 90 MB 的磁盘合并。如果生产环境有足够的内存,您可能需要增加相关设置(工作内存?)以在内存中进行。它似乎是 work_mem 参数 here,但您需要通读其余部分。

我还建议在index usage statistics 使用look。存在许多带有部分和功能索引的选项。

【讨论】:

  • EXPLAIN ANALYZE 输出编辑了我的问题。
【解决方案3】:

试试

SET enable_seqscan TO 'off'

EXPLAIN ANALYZE之前

【讨论】:

  • 请注意,如果您解释说这是一种故障排除技术,用于确定使用索引扫描而不是盲目地应用于数据库的修复程序实际上是否会更快查询,这将是一个很大的问题更好的答案。
【解决方案4】:

在我看来,您的开发数据比生产数据“简单”得多。举个例子:

发展:

->  Index Scan using index_scenario_ad_group_vendor_instances_on_ad_group_id on scenario_ad_group_vendor_instances  
(cost=0.00..1737.02 rows=27447 width=8) 
(actual time=0.007..7.021 rows=16528 loops=1)
Filter: (ad_vendor_id = ANY ('{1,2,3}'::integer[]))

生产:

->  Seq Scan on scenario_ad_group_vendor_instances  
(cost=0.00..11336.31 rows=317174 width=8) 
(actual time=0.020..451.496 rows=431770 loops=1)
Filter: (ad_vendor_id = ANY ('{1,2,3}'::integer[]))

这意味着,在 dev 27447 匹配行已预先估计,并且确实找到了 16528 行。那不是同一个球场,好吧。

在生产中,预先估计了 317174 行匹配,并找到了 431770 行。也可以。

但将 dev 与 prod 进行比较意味着数字相差 10 倍。正如其他答案已经表明的那样,进行 10 倍的随机搜索(由于索引访问)可能确实比普通的表扫描更糟糕。

因此有趣的问题是:该表在 dev 和 prod 中包含多少行? number_returned_rows / number_total_rows 在 dev 和 prod 之间是否具有可比性?

编辑不要忘记:我选择了 one 索引访问作为示例。快速浏览一下,其他索引访问也有相同的症状。

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2012-05-21
    • 1970-01-01
    • 2019-10-05
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多