【问题标题】:Postgres query slow with many columnsPostgres查询速度慢,列多
【发布时间】:2019-01-26 23:24:11
【问题描述】:

我有一个返回 10 行的查询(使用 psql 对 Postgres (9.6.10) 数据库执行)。选择 30 列而不是 1 列时,查询的执行速度要慢 20 倍。

我想我知道为什么会发生这种情况(请参阅下面的 EXPLAIN 输出)。我猜解决方法是只选择 id 然后重新加入数据。这是否表明查询计划器中存在错误?还有其他解决方法吗?

查询 1(20 秒后执行)

EXPLAIN ANALYZE SELECT fundraisers.*
FROM fundraisers 
INNER JOIN audit_logs ON audit_logs.fundraiser_id = fundraisers.id 
LEFT OUTER JOIN accounts ON accounts.id = fundraisers.account_id 
GROUP BY accounts.id, fundraisers.id  
LIMIT 10

查询 2(1 秒后执行)

仅在选定的列中有所不同

EXPLAIN ANALYZE SELECT fundraisers.id
FROM fundraisers 
INNER JOIN audit_logs ON audit_logs.fundraiser_id = fundraisers.id 
LEFT OUTER JOIN accounts ON accounts.id = fundraisers.account_id 
GROUP BY accounts.id, fundraisers.id  
LIMIT 10

解释输出

我注意到的一件事是,在 EXPLAIN 输出中,我看到由于要连接的数据的宽度,哈希连接具有不同的成本。即。

->  Hash Join  (cost=25967.06..109216.83 rows=1359646 width=1634) (actual time=322.987..1971.464 rows=1356192 loops=1)

->  Hash Join  (cost=14500.06..74422.83 rows=1359646 width=8) (actual time=111.710..730.736 rows=1356192 loops=1)

更多详情

database=# EXPLAIN ANALYZE SELECT fundraisers.*
database-# FROM fundraisers 
database-# INNER JOIN audit_logs ON audit_logs.fundraiser_id = fundraisers.id 
database-# LEFT OUTER JOIN accounts ON accounts.id = fundraisers.account_id 
database-# GROUP BY accounts.id, fundraisers.id  
database-# LIMIT 10;
                                                                       QUERY PLAN                                                                        
---------------------------------------------------------------------------------------------------------------------------------------------------------
 Limit  (cost=3147608.91..3147608.98 rows=10 width=1634) (actual time=20437.137..20437.190 rows=10 loops=1)
   ->  Group  (cost=3147608.91..3157806.25 rows=1359646 width=1634) (actual time=20437.136..20437.186 rows=10 loops=1)
         Group Key: accounts.id, fundraisers.id
         ->  Sort  (cost=3147608.91..3151008.02 rows=1359646 width=1634) (actual time=20437.133..20437.165 rows=120 loops=1)
               Sort Key: accounts.id, fundraisers.id
               Sort Method: external merge  Disk: 1976192kB
               ->  Hash Join  (cost=25967.06..109216.83 rows=1359646 width=1634) (actual time=322.987..1971.464 rows=1356192 loops=1)
                     Hash Cond: (audit_logs.fundraiser_id = fundraisers.id)
                     ->  Seq Scan on audit_logs  (cost=0.00..40634.14 rows=1517914 width=4) (actual time=0.078..324.638 rows=1517915 loops=1)
                     ->  Hash  (cost=13794.41..13794.41 rows=56452 width=1634) (actual time=321.869..321.869 rows=56452 loops=1)
                           Buckets: 4096  Batches: 32  Memory Usage: 2786kB
                           ->  Hash Left Join  (cost=1548.76..13794.41 rows=56452 width=1634) (actual time=16.465..122.406 rows=56452 loops=1)
                                 Hash Cond: (fundraisers.account_id = accounts.id)
                                 ->  Seq Scan on fundraisers  (cost=0.00..11546.52 rows=56452 width=1630) (actual time=0.068..54.434 rows=56452 loops=1)
                                 ->  Hash  (cost=965.56..965.56 rows=46656 width=4) (actual time=16.337..16.337 rows=46656 loops=1)
                                       Buckets: 65536  Batches: 1  Memory Usage: 2153kB
                                       ->  Seq Scan on accounts  (cost=0.00..965.56 rows=46656 width=4) (actual time=0.020..8.268 rows=46656 loops=1)

 Planning time: 0.748 ms
 Execution time: 21013.427 ms
(19 rows)

database=# EXPLAIN ANALYZE SELECT fundraisers.id
database-# FROM fundraisers 
database-# INNER JOIN audit_logs ON audit_logs.fundraiser_id = fundraisers.id 
database-# LEFT OUTER JOIN accounts ON accounts.id = fundraisers.account_id 
database-# GROUP BY accounts.id, fundraisers.id  
database-# LIMIT 10;
                                                                      QUERY PLAN                                                                      
------------------------------------------------------------------------------------------------------------------------------------------------------
 Limit  (cost=231527.41..231527.48 rows=10 width=8) (actual time=1314.884..1314.917 rows=10 loops=1)
   ->  Group  (cost=231527.41..241724.75 rows=1359646 width=8) (actual time=1314.884..1314.914 rows=10 loops=1)
         Group Key: accounts.id, fundraisers.id
         ->  Sort  (cost=231527.41..234926.52 rows=1359646 width=8) (actual time=1314.883..1314.901 rows=120 loops=1)
               Sort Key: accounts.id, fundraisers.id
               Sort Method: external merge  Disk: 23840kB
               ->  Hash Join  (cost=14500.06..74422.83 rows=1359646 width=8) (actual time=111.710..730.736 rows=1356192 loops=1)
                     Hash Cond: (audit_logs.fundraiser_id = fundraisers.id)
                     ->  Seq Scan on audit_logs  (cost=0.00..40634.14 rows=1517914 width=4) (actual time=0.062..224.307 rows=1517915 loops=1)
                     ->  Hash  (cost=13794.41..13794.41 rows=56452 width=8) (actual time=111.566..111.566 rows=56452 loops=1)
                           Buckets: 65536  Batches: 1  Memory Usage: 2687kB
                           ->  Hash Left Join  (cost=1548.76..13794.41 rows=56452 width=8) (actual time=17.362..98.257 rows=56452 loops=1)
                                 Hash Cond: (fundraisers.account_id = accounts.id)
                                 ->  Seq Scan on fundraisers  (cost=0.00..11546.52 rows=56452 width=8) (actual time=0.067..54.676 rows=56452 loops=1)
                                 ->  Hash  (cost=965.56..965.56 rows=46656 width=4) (actual time=16.524..16.524 rows=46656 loops=1)
                                       Buckets: 65536  Batches: 1  Memory Usage: 2153kB
                                       ->  Seq Scan on accounts  (cost=0.00..965.56 rows=46656 width=4) (actual time=0.032..7.804 rows=46656 loops=1)
 Planning time: 0.469 ms
 Execution time: 1323.349 ms

【问题讨论】:

  • 你放GROUP BY的原因是什么?
  • 在单列情况下,accounts 表与返回的集合无关。完整的解释是否显示了它的任何用途?大量的顺序扫描让我们怀疑你是否在accountsfundraisers 中索引了外键。
  • @lau - GROUP BY 的原因是为了获得不同的筹款活动。 (我相信这不是标准的,但可以在 postgres 中使用)。
  • @FelixLivni 我将您的评论解释为“我上网是为了浪费专家 DBA 的时间来询问一些不应该被测试的东西,我什至严重削弱了这个问题的比较是无意义的。”你为什么不重写这个关于你的任务需要什么的问题呢?同时,我给出的问题是 (–1),我很少对非垃圾邮件这样做。
  • @AndrewLazarus 根本不是我的意图。对不起,如果我浪费了你的时间!背景:我试图改进最初是现实世界问题的东西。我看到了我认为奇怪的行为。我删除了所有实用的部分,最终得到了问题的最简单版本,它仍然会表现出相同的行为并发布。因此,问题不再是“我如何提高查询的性能”,而是“为什么我会看到这种奇怪的行为”?抱歉,如果它看起来缺乏实际应用。

标签: postgresql performance


【解决方案1】:

第一:

  • 没有键的表没有意义(这是第二范式的结果)
  • 对此类表的查询(结果)没有意义
  • 没有任何结构(PK、FK、二级索引),优化器只有两个选项:嵌套循环(在 seqscans 上)或 hashjoins
  • hashjoins 总是一个不错的选择,有足够的内存
  • 最终的ORDER BYGROUP BY 需要对完整结果集(仅用于查找前10 个结果)进行排序步骤(哈希连接的结果没有隐式顺序)
  • 如果哈希表变得太大(大于WORK_MEM),它将溢出到磁盘
  • 更多列需要更多空间,即使在哈希表中也是如此,因此它们会更快超过WORK_MEM,并溢出到磁盘

最后:基准和比较无意义的查询毫无意义。整个优化器机制都假设了健全的数据模型。没有这个,它只会产生一些有效的东西

【讨论】:

  • 另见我对这个问题的新评论。
【解决方案2】:

您在分析中缺少的是排序成本。

正在发生的事情的顺序是:

  1. 从表中选择数据 + JOIN(相当昂贵)
  2. 对数据进行排序,为GROUP BY做准备。
  3. GROUP BY(由于排序便宜)+ LIMIT 按要求。

我无法获得此 sort 的文档,因此我假设它的工作方式类似于我稍微了解的另一个 DBMS:Oracle。

正如here 解释的那样,服务器有时需要使用硬盘驱动器进行此操作。
这是一个非常缓慢的操作。

很可能,这就是您的查询所发生的情况,不同之处在于 postgresql 将有 1 个字段(= 总执行时间为 1 秒)或许多(= 总执行时间为 20 秒)要写入。


话虽如此,请记住您只是在使用测试查询,可能相当于SELECT * FROM fundraisers LIMIT 10(根据字段名称,我不确定表的定义)。

对于您想要的(= 生产查询)和您输入的(= 测试查询)之间存在如此大的差距,我并不感到震惊,数据库的行为有点滑稽。

【讨论】:

  • 我很感兴趣,为什么查询 1 和查询 2 之间的相对性能不同,因为它们仅在列数上有所不同。我的猜测是,如果只返回 10 条记录,则使用不同的查询计划,性能将是相同的。也就是说,一个不同的查询计划基本上可以做我提到的解决方法:只选择一列然后重新加入。
猜你喜欢
  • 1970-01-01
  • 2021-08-30
  • 2014-03-24
  • 2021-10-28
  • 2018-04-17
  • 1970-01-01
  • 2021-04-10
  • 2019-08-13
  • 2011-10-04
相关资源
最近更新 更多