为什么此查询使用错误的索引?

问题描述 投票:0回答:1

我目前在同一查询的两个不同主机(本地和远程)上使用Postgresql的索引使用问题。有问题的查询如下:

SELECT COUNT(*) 
FROM (
  SELECT  1 AS one 
  FROM "big_table" 
  WHERE "big_table"."user_id" = 13 
    AND "big_table"."action" = 1 
    AND (big_table.created_at >= '2018-12-09 23:00:00'::timestamp without time zone) 
  ORDER BY big_table.created_at desc LIMIT 15 OFFSET 10
) subquery_for_count;

我更改此查询超出了范围,因为它是由我们正在使用的库生成的,所以我想找到一个解决方案而不必更改它。如果我通过上述查询在本地运行EXPLAIN命令,我的Postgres实例将输出以下内容:

local_host=# EXPLAIN SELECT COUNT(*) FROM (SELECT  1 AS one FROM "big_table" WHERE "big_table"."user_id" = 13 AND "big_table"."action" = 1 AND (big_table.created_at >= '2018-12-09 23:00:00'::timestamp without time zone) ORDER BY big_table.created_at desc LIMIT 15 OFFSET 10) subquery_for_count;
                                                              QUERY PLAN                                                              
--------------------------------------------------------------------------------------------------------------------------------------
 Aggregate  (cost=8.59..8.60 rows=1 width=8)
   ->  Limit  (cost=8.57..8.58 rows=1 width=12)
         ->  Sort  (cost=8.57..8.57 rows=1 width=12)
               Sort Key: big_table.created_at DESC
               ->  Index Scan using big_table_idx_user_action_transfers on big_table  (cost=0.56..8.56 rows=1 width=12)
                     Index Cond: ((user_id = 13) AND (action = 1))
                     Filter: (created_at >= '2018-12-09 23:00:00'::timestamp without time zone)
(7 rows)

[这很好,它(部分)按预期使用user_idaction上的复合索引。但是,如果我在远程系统上运行查询,则会得到以下EXPLAIN输出:

remote_host=# EXPLAIN SELECT COUNT(*) FROM (SELECT  1 AS one FROM "big_table" WHERE "big_table"."user_id" = 13 AND "big_table"."action" = 1 AND (big_table.created_at >= '2018-12-09 23:00:00'::timestamp without time zone) ORDER BY big_table.created_at desc LIMIT 15 OFFSET 10) subquery_for_count;
                                                                 QUERY PLAN                                                                  
---------------------------------------------------------------------------------------------------------------------------------------------
 Aggregate  (cost=8472.67..8472.68 rows=1 width=8)
   ->  Limit  (cost=3389.25..8472.48 rows=15 width=12)
         ->  Index Scan Backward using index_big_table_on_created_at on big_table  (cost=0.44..4492554.51 rows=13257 width=12)
               Index Cond: (created_at >= '2018-12-09 23:00:00'::timestamp without time zone)
               Filter: ((user_id = 13) AND (action = 1))
(5 rows)

正如您所看到的,在远程主机上,数据库使用created_at上的索引,而不是user_idaction作为本地安装。这会导致此查询在远程主机上的接收速度缓慢(要完成> 1分钟),因为有很多满足索引条件的条目,而过滤所有这些条目都需要花费大量时间。但是,在我的本地安装上,速度非常快(大约需要1秒)。我的本地表和远程表都具有相同数量的条目(〜25mio。),并且数据分布大致相同。我们在远程主机上运行Vacuum守护程序,因此VACUUM ANALYZE的执行非常频繁。另外,两个系统上的索引设置完全相同。

我已经尝试搜索该问题的解决方案,但是直到运行VACUUM ANALYZE并确保存在相关属性的索引,我才发现有用的东西。

也许你们有一个线索?当然,我可以为所有使用的属性(user_idactioncreated_at)添加一个复合索引,但是对于为什么在这种情况下在远程服务器上不使用“正确的”索引,我仍然很困惑主机。

两个主机都使用PostgreSQL的9.6版本(确切地说,本地主机上的9.6.9和远程主机上的9.6.17

postgresql query-performance
1个回答
1
投票

它可以使用一个索引来提供过滤器,然后进行排序。或者它可以使用另一个提供ORDER BY,然后根据LIMIT提前停止。它必须选择,因为不能同时选择两者。 PostgreSQL无法知道所有带有"big_table"."user_id" = 13 AND "big_table"."action" = 1的东西也是很久以前创建的,因此它不知道基于LIMIT的提前停止实际上不会很早就结束。

很难弄清楚你的问题是什么。您似乎知道答案是什么,在(user_id, action, created_at)上建立索引。如果您想解决性能问题,也可以这样做。

您说您很困惑,也无法更改查询。能够更改来自您的应用程序的查询与解决您的困惑无关。即使不幸的工具限制使您无法实施解决方案,也不会阻止您了解解决方案或问题。

您是寻求了解还是解决方案?

我的本地表和远程表都具有相同数量的条目(〜25mio。),并且具有相同的数据分布

数据分发有很多方面。也许它们在某些方面是相似的,但在另一些情况下却不是。看到两台服务器的EXPLAIN (ANALYZE, BUFFERS)输出确实有帮助,但可能还不够。在使用快速计划的情况下,从慢速服务器中看到EXPLAIN (ANALYZE, BUFFERS)也很不错。您可以通过删除错误的索引或更改查询以使其使用ORDER BY (big_table.created_at + interval '0') desc来执行此操作。您不需要让您的应用程序运行此查询,就可以手动运行它。

再三考虑,看到运行慢速计划的快速服务器的EXPLAIN (ANALYZE, BUFFERS)甚至可能是[[more有用的。您可能可以通过将查询更改为使用...WHERE ("big_table"."user_id" + 0 = 13) AND...

来完成此操作
© www.soinside.com 2019 - 2024. All rights reserved.