【问题标题】:Why Postgres is taking so much time in index only scan为什么 Postgres 在仅索引扫描中花费这么多时间
【发布时间】:2021-06-01 19:09:00
【问题描述】:

Postgres 版本:12

查询

解释(ANALYZE TRUE, VERBOSE TRUE, COSTS TRUE, BUFFERS TRUE, TIMING TRUE) SELECT MIN("id"), MAX("id") FROM "public"."hotel_slot_inventory" WHERE ("updated_at" >= '2021-03-02 13:30:03' AND "updated_at"

查询计划:

 Result  (cost=512.17..512.18 rows=1 width=8) (actual time=65556.920..65556.926 rows=1 loops=1)
   Output: $0, $1
   Buffers: shared hit=370 read=454012 written=8
   I/O Timings: read=62266.717 write=0.194
   InitPlan 1 (returns $0)
     ->  Limit  (cost=0.57..256.09 rows=1 width=4) (actual time=65251.998..65252.001 rows=1 loops=1)
           Output: hotel_slot_inventory.id
           Buffers: shared hit=1 read=453546 written=8
           I/O Timings: read=61967.042 write=0.194
           ->  Index Only Scan using hotel_slot_inventory_id_updated_at_idx on public.hotel_slot_inventory  (cost=0.57..3291347.07 rows=12881 width=4) (actual time=65251.996..65251.997 rows=1 loops=1)
                 Output: hotel_slot_inventory.id
                 Index Cond: ((hotel_slot_inventory.id IS NOT NULL) AND (hotel_slot_inventory.updated_at >= '2021-03-02 13:30:03'::timestamp without time zone) AND (hotel_slot_inventory.updated_at < '2021-03-03 06:15:19.127884'::timestamp without time zone))
                 Heap Fetches: 1
                 Buffers: shared hit=1 read=453546 written=8
                 I/O Timings: read=61967.042 write=0.194
   InitPlan 2 (returns $1)
     ->  Limit  (cost=0.57..256.09 rows=1 width=4) (actual time=304.902..304.903 rows=1 loops=1)
           Output: hotel_slot_inventory_1.id
           Buffers: shared hit=369 read=466
           I/O Timings: read=299.674
           ->  Index Only Scan Backward using hotel_slot_inventory_id_updated_at_idx on public.hotel_slot_inventory hotel_slot_inventory_1  (cost=0.57..3291347.07 rows=12881 width=4) (actual time=304.899..304.899 rows=1 loops=1)
                 Output: hotel_slot_inventory_1.id
                 Index Cond: ((hotel_slot_inventory_1.id IS NOT NULL) AND (hotel_slot_inventory_1.updated_at >= '2021-03-02 13:30:03'::timestamp without time zone) AND (hotel_slot_inventory_1.updated_at < '2021-03-03 06:15:19.127884'::timestamp without time zone))
                 Heap Fetches: 3892
                 Buffers: shared hit=369 read=466
                 I/O Timings: read=299.674
 Planning Time: 0.229 ms
 Execution Time: 65556.982 ms
(28 rows)

我们可以看到这个简单的仅索引扫描花费了 65556.982 毫秒。 InitPlan 1 65251.997 ms 花费了大部分时间。为什么会这样?它只需要分别从 btree 索引向前和向后搜索中获取第一条记录,因为查询要求 Min 和 Max...不需要从 btree 索引中获取所有匹配记录
仅供参考: 真空并没有太大帮助。

编辑索引膨胀详细信息:

real_size:3751411712 = 3.49 GB

额外大小:470237184 = 448 MB

额外比率:12.53

填充因子:90

膨胀大小:107053056 = 102 MB

bloat_ratio:2.85

表膨胀大小: 膨胀大小:475283456 = 453 MB

膨胀率:5.088

【问题讨论】:

  • 请不要针对同一个问题打开不同的问题:stackoverflow.com/questions/66452891/…
  • @S-Man:在这两个线程中,我都在问不同的问题。这是以前向我推荐过的,我觉得它是合理的。
  • 你应该给我们索引定义而不是让我们猜测。
  • 您没有回答要求提供有关重复问题的更多信息的问题...我要求您检查索引膨胀的统计信息,因为它必须读取 453546 个缓冲区才能获得一行是不正常的。
  • @bobflux:刚刚做了

标签: postgresql query-optimization explain


【解决方案1】:

您的索引必须以(id, updated_at...) 开头。请注意,此索引不能仅读取有问题的时间范围,因为那不是索引中的第一列,并且第一列不是由相等性指定的。所以你开始扫描整个索引,直到找到满足时间条件的行。我称之为索引内过滤器。显然,这需要大量的正向扫描,那是因为索引那一端的记录都不符合时间条件。然而,规划器并不理解这个事实,它认为它会找到均匀分布在整个索引中的 12881 行。不是全部。

与常规(索引外)过滤器不同,该计划不报告有多少行被评估然后被索引内过滤器删除。这使得计划有点难以解释。

这种解释有两个证据。一是即使您的查询没有指定限制,限制节点仍然存在。那只能支持最小和最大聚合。另一种是在索引条件中注入 IS NOT NULL,而这不在您的查询中。我不知道为什么会这样,但它确实表明了索引内过滤器(或有时是部分索引),而不是普通的索引使用。

【讨论】:

  • 是的,我的索引在(id,updated_at)。我以这种方式创建它是因为之前我像 (updated_at,id) 一样创建它,但查询根本没有使用它,而且查询运行很慢很多分钟,所以我恢复了索引位置,然后它最终使用了索引,但那是慢。不知道怎么解决
  • 请在这里提供帮助。
  • (updated_at,id) 上的索引在这个时间范围内应该很快,假设统计数据是准确的并且在那个时间范围内确实只有 12881 行。如果你对此有任何疑问,你应该发布它。 (不是您上周发布的那些您在甚至不是您正在查询的表上创建索引的杂乱无章的混乱。)
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2017-11-15
  • 2016-08-24
  • 1970-01-01
  • 2013-08-26
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多