Postgres使用位图索引扫描而不是常规的索引扫描

时间:2019-10-17 23:57:09

标签: postgresql

我有两个Postgres表:一个普通表(2000万行)和一个物化视图(200万行)。这两个表在“时间”列上都有一个索引。在这两个表上,我正在运行一个查询,该查询在一个日期范围内进行汇总。当我在普通表上运行查询时,Postgres使用索引扫描,大约需要1秒钟。但是,在实例化视图上,Postgres使用位图索引扫描,该过程大约需要7秒钟。我不确定为什么这里没有使用普通的索引扫描。

两个表大致相似;物化视图具有3个额外的列(2个浮点数和一个布尔值)。这些表都在其id列和time列上都有索引;在普通表中,id列是主键。两个表都已VACUUM ANALYZE d。

查询如下:

SELECT count(*) as post_count,
    sum(CASE WHEN in_reply_to_post_id IS NULL THEN 0 ELSE 1 END) as replies,
    date_trunc('hour', time) as time_interval
FROM posts_enriched_materialized_2_weeks
WHERE time >= '2019-10-10' AND time < '2019-10-11'
GROUP BY time_interval;

在物化视图上EXPLAIN ANALYZE的结果如下:

GroupAggregate  (cost=264262.43..268387.60 rows=158191 width=32) (actual time=7794.743..7893.961 rows=24 loops=1)
  Group Key: (date_trunc('hour'::text, "time"))
  ->  Sort  (cost=264262.43..264691.99 rows=171822 width=46) (actual time=7790.080..7838.184 rows=175691 loops=1)
        Sort Key: (date_trunc('hour'::text, "time"))
        Sort Method: external merge  Disk: 5464kB
        ->  Bitmap Heap Scan on posts_enriched_materialized_2_weeks  (cost=4057.61..244033.53 rows=171822 width=46) (actual time=21.184..7672.166 rows=175691 loops=1)
              Recheck Cond: (("time" >= '2019-10-10 00:00:00'::timestamp without time zone) AND ("time" < '2019-10-11 00:00:00'::timestamp without time zone))
              Heap Blocks: exact=17420
              ->  Bitmap Index Scan on posts_enriched_materialized_2_weeks_time_index  (cost=0.00..4014.65 rows=171822 width=0) (actual time=18.551..18.551 rows=175691 loops=1)
                    Index Cond: (("time" >= '2019-10-10 00:00:00'::timestamp without time zone) AND ("time" < '2019-10-11 00:00:00'::timestamp without time zone))
Planning time: 0.106 ms
Execution time: 7894.874 ms

编辑:非MV表上EXPLAIN ANALYZE的结果如下:

GroupAggregate  (cost=193490.77..197635.89 rows=150641 width=32) (actual time=1168.018..1267.225 rows=24 loops=1)
  Group Key: (date_trunc('hour'::text, "time"))
  ->  Sort  (cost=193490.77..193943.19 rows=180969 width=46) (actual time=1163.293..1210.887 rows=175701 loops=1)
        Sort Key: (date_trunc('hour'::text, "time"))
        Sort Method: external merge  Disk: 5472kB
        ->  Index Scan using posts_time_index on tweets  (cost=0.44..172118.80 rows=180969 width=46) (actual time=0.900..1065.469 rows=175701 loops=1)
              Index Cond: (("time" >= '2019-10-10 00:00:00'::timestamp without time zone) AND ("time" < '2019-10-11 00:00:00'::timestamp without time zone))
Planning time: 0.514 ms
Execution time: 1268.219 ms

以下是注释中要求的每个表的time列的相关性。我不完全知道这个值是什么意思,但是它看起来非常有用:

posts   0.8844374
posts_enriched_materialized_2_weeks 0.09846322

1 个答案:

答案 0 :(得分:2)

由于相关性很低,计划者认为如果进行普通索引扫描,它将在整个表(实例化视图)上跳跃以获取指示的行,从而导致大量随机IO,这比顺序运行慢得多IO通过进行位图扫描,它可以改善这种情况,因为位图固有地将索引中的行“排序”为它将在表中找到行的顺序,从而使表的读取更加连续。

具有很高的相关性,自然会在进行常规索引扫描时或多或少地按顺序读取表,因为表和索引的顺序大致相同。同样,它将读取表的一小部分。如果相关性是完美的,并且您读取索引的1/100,则您将仅读取构成该表的页面的大约1/100,并且将按顺序进行。因为您已经在进行顺序读取,所以切换到位图扫描不会给您带来任何好处,但是确实需要一定的成本。

在您的情况下,这种改善似乎效果不佳。可能是您所需的数据在物化视图中足够稀疏,因此从IO系统的角度来看,按顺序读取数据仍然比顺序读取更具随机性。

另一个问题可能是您的表经常被使用,因此在缓存中“热”,而您的物化视图很少被使用,因此“冷”。这不会解释为什么选择位图扫描,但是会解释为什么位图扫描效果不佳。

您可以在实例化视图的定义中添加“按时间排序”,以按时间列对其进行聚类。

如果那不能解决问题,那么增加“ effective_io_concurrency”可能会有所帮助,特别是如果您具有RAID或JBOD。通过使位图堆扫描尝试从所有不同的主轴中预取页面,可以提高IO吞吐量。