为啥这个 pg 查询这么慢?我怎样才能让它更快?

Posted

技术标签:

【中文标题】为啥这个 pg 查询这么慢?我怎样才能让它更快?【英文标题】:Why is this pg query so slow? How can I make it faster?为什么这个 pg 查询这么慢?我怎样才能让它更快? 【发布时间】:2019-09-28 04:51:03 【问题描述】:

这是查询:

(SELECT * 
FROM url 
WHERE domain = 'youtube.com' 
  AND timestamp > NOW() - INTERVAL '24 hours' 
ORDER BY likes DESC LIMIT 10) 
UNION 
(SELECT * 
FROM url 
WHERE domain = 'twitter.com' 
  AND timestamp > NOW() - INTERVAL '24 hours' 
ORDER BY likes DESC LIMIT 10) 
UNION 
(SELECT * 
FROM url 
WHERE domain = 'reddit.com' 
  AND timestamp > NOW() - INTERVAL '24 hours' 
ORDER BY likes DESC LIMIT 10) 
ORDER BY timestamp DESC

这是EXPLAIN ANALYZE

Sort  (cost=20460.17..20460.25 rows=30 width=497) (actual time=5161.013..5161.015 rows=30 loops=1)
  Sort Key: url."timestamp" DESC
  Sort Method: quicksort  Memory: 53kB
  ->  HashAggregate  (cost=20459.14..20459.44 rows=30 width=497) (actual time=5160.709..5160.738 rows=30 loops=1)
        Group Key: url.url, url.domain, url.title, url.views, url.likes, url.dislikes, url.comments, url.shares, url.links_to_url, url."user", url.thumbnail_url, url.is_collection, url.image_url, url.video_url, url.audio_url, url.width, url.height, url.body, url.source, url."timestamp", url.created_at, url.updated_at, url.duration_seconds, url.tags, url.channel
        ->  Append  (cost=0.43..20457.26 rows=30 width=497) (actual time=0.514..5160.073 rows=30 loops=1)
              ->  Limit  (cost=0.43..18150.71 rows=10 width=1177) (actual time=0.513..28.599 rows=10 loops=1)
                    ->  Index Scan Backward using "url-likes-index" on url  (cost=0.43..816763.00 rows=450 width=1177) (actual time=0.511..28.594 rows=10 loops=1)
                          Filter: (((domain)::text = 'youtube.com'::text) AND ("timestamp" > (now() - '24:00:00'::interval)))
                          Rows Removed by Filter: 11106
              ->  Limit  (cost=0.43..859.82 rows=10 width=1177) (actual time=2330.390..5033.214 rows=10 loops=1)
                    ->  Index Scan Backward using "url-likes-index" on url url_1  (cost=0.43..816763.00 rows=9504 width=1177) (actual time=2330.388..5033.200 rows=10 loops=1)
                          Filter: (((domain)::text = 'twitter.com'::text) AND ("timestamp" > (now() - '24:00:00'::interval)))
                          Rows Removed by Filter: 1667422
              ->  Limit  (cost=0.43..1446.28 rows=10 width=1177) (actual time=64.748..98.228 rows=10 loops=1)
                    ->  Index Scan Backward using "url-likes-index" on url url_2  (cost=0.43..816763.00 rows=5649 width=1177) (actual time=64.745..98.220 rows=10 loops=1)
                          Filter: (((domain)::text = 'reddit.com'::text) AND ("timestamp" > (now() - '24:00:00'::interval)))
                          Rows Removed by Filter: 26739
Planning Time: 3.006 ms
Execution Time: 5162.201 ms

如果您有兴趣自己运行它,go to this link。

我看到一百万条推特行正在被过滤,但我不确定如何避免它。我有一个timestamp 索引,我希望可以使用它而不是按likes 排序并扫描整个内容。这是否意味着我需要一个复合索引?有没有办法让规划器使用两个索引而不是另一个?

附言我想我把主键作为 url 搞砸了。它使索引不必要地变大。

【问题讨论】:

UNION ALL 而不是 UNION 可能会改进一点。 很难判断,因为数字很紧张,但可能会有 5% 的变化。谢谢你提到它。 @a_horse_with_no_name 。 . .虽然是最佳实践,但 unionunion all 在 30 行上并不会产生很大的不同。 【参考方案1】:

PostgreSQL 尝试使用likes 上的索引来避免排序以获得前 10 个结果,但它必须丢弃许多行才能到达那里。

也许执行计划是最好的,也许不是。

按照以下步骤操作:

    在你的桌子上运行ANALYZE,看看是否能解决问题。

    如果没有,请在 (domain, timestamp) 上创建一个索引(按此顺序!),看看这是否会改善问题。

    如果这还不够,也可以

    删除likes 上的索引(如果可以的话)

    ORDER BY likes 更改为ORDER BY likes + 0

如果这一切都没有让它变得更好,那么您最初的查询计划是最好的,您所能做的就是使用更多的 RAM,希望更多的数据在缓存中。

【讨论】:

刚刚尝试过ORDER BY likes + 0,这已经导致 pg 避免使用likes 索引并将我从 20 秒缩短到 0.25。我会测试你的其他想法,但这已经值得了。谢谢! 我建议最后一个,因为它是最不优雅的解决方案。 ANALYZE url 似乎什么也没做。我的索引有点大,所以我有点害羞,但以后可能会尝试。 好吧,那就选择第三个吧。【参考方案2】:

我建议这样编写查询:

SELECT ufiltered.*
FROM (SELECT url.*,
            ROW_NUMBER() OVER (PARTITION BY domain ORDER BY likes DESC) AS seqnum
      FROM url 
      WHERE domain IN ('youtube.com', 'twitter.com', 'reddit.com') AND
            timestamp > NOW() - INTERVAL '24 hours'
    ) AS ufiltered
WHERE seqnum <= 10
ORDER BY timestamp DESC

为此,我建议在url(timestamp, domain, likes) 上建立索引。

【讨论】:

我不得不编辑,因为seqnum 没有定义,但是这个查询看起来相当快,没有使用views+0 技巧,而且它短了大约 100 个字符。我会接受的。 当我将它与1 year 一起使用时,我得到了QueryFailedError: temporary file size exceeds temp_file_limit (3193019kB)。所以我最终使用了问题中的原始查询,没有强制索引。不同的时间范围各自需要不同的索引。

以上是关于为啥这个 pg 查询这么慢?我怎样才能让它更快?的主要内容,如果未能解决你的问题,请参考以下文章

为啥这个计数查询这么慢?

在 R 中模拟 - 我怎样才能让它更快?

为啥这个字典理解这么慢?请建议加快速度的方法

为啥这个查询运行这么慢?

在 OpenCl 中,多个 gpu 比单个 gpu 慢。我怎样才能更快?

为啥这个查询这么慢? - PostgreSQL - 从 SERIAL、TIMESTAMP 和 NUMERIC(6,2) 中选择