按时间范围查询时的Postgresql性能问题

时间:2019-07-16 15:16:38

标签: sql postgresql

我试图了解Postgres(v10.9)上一个奇怪的性能问题。

我们有一个requests表,我想在几个时间范围内获取一组特定用户的所有请求。以下是相关信息:

  • 表中没有user_id列。而是有一个名为params的jsonb列,其中user_id字段存储为字符串。
  • 所涉及的用户数量非常庞大,成千上万。
  • 有一列time类型为timestamptz的列,并使用标准BTREE索引对其进行了索引。
  • params->>'user_id'上还有一个单独的BTREE索引。

我正在运行的查询基于以下模板:

SELECT *
FROM requests
WHERE params->>'user_id' = ANY (VALUES ('id1'), ('id2'), ('id3')...)
AND time > 't1' AND time < 't2'

此处的ID和时间是实际ID和时间的占位符。

我正在连续两个时间范围(每个2周)内运行这样的查询。前几个时间范围的查询每个都需要花费几分钟,这在生产方面显然很长,但对于研究目的来说可以。然后突然之间查询运行时出现了急剧的高峰,并且每次启动都耗时数小时,即使对于脱机目的,也变得站不住脚了。

此峰值每次都在相同范围内发生。值得注意的是,在此时间范围内,请求总数增加了x1.5。当然,与之前的时间范围相比,它的数量更多,但不足以保证整个数量级的峰值。

以下是在合理的运行时间下最后一个时间范围内EXPLAIN ANALYZE的输出:

Hash Join  (cost=442.69..446645.35 rows=986171 width=1217) (actual time=66.305..203593.238 rows=445175 loops=1)
  Hash Cond: ((requests.params ->> 'user_id'::text) = \"*VALUES*\".column1)
  ->  Index Scan using requests_time_idx on requests  (cost=0.56..428686.19 rows=1976888 width=1217) (actual time=14.336..201643.439 rows=2139604 loops=1)
        Index Cond: ((\"time\" > '2019-02-12 22:00:00+00'::timestamp with time zone) AND (\"time\" < '2019-02-26 22:00:00+00'::timestamp with time zone))
  ->  Hash  (cost=439.62..439.62 rows=200 width=32) (actual time=43.818..43.818 rows=29175 loops=1)
        Buckets: 32768 (originally 1024)  Batches: 1 (originally 1)  Memory Usage: 2536kB
        ->  HashAggregate  (cost=437.62..439.62 rows=200 width=32) (actual time=24.887..33.775 rows=29175 loops=1)
              Group Key: \"*VALUES*\".column1
              ->  Values Scan on \"*VALUES*\"  (cost=0.00..364.69 rows=29175 width=32) (actual time=0.006..10.303 rows=29175 loops=1)
Planning time: 133.807 ms
Execution time: 203697.360 ms

如果我正确理解这一点,似乎大部分时间都用于按时间范围过滤请求,即使:

  • 似乎使用了时间索引。
  • 当在用户身上没有过滤器的情况下运行相同的查询时(基本上只是按时间范围获取所有请求),它们都在正常时间内运行。

感谢您对如何解决此问题的任何想法,谢谢!

1 个答案:

答案 0 :(得分:0)

由于要检索的行太多,因此查询永远不会很快。

不幸的是,没有一个索引可以同时满足这两个条件,但是您可以使用以下两个条件:

CREATE INDEX ON requests ((params->>'user_id'));
CREATE INDEX ON requests (time);

然后,您可以希望进行两次位图索引扫描,并通过“位图或”进行连接。

我不确定这是否会提高性能; PostgreSQL仍然可以选择当前的计划,这不是一个坏主意。如果缓存了索引或快速访问了存储,请相应地设置effective_cache_sizerandom_page_cost,这将使PostgreSQL倾向于索引扫描。