【问题标题】:Why does adding a window function make this query so slow?为什么添加窗口函数会使这个查询变得如此缓慢?
【发布时间】:2014-09-06 21:25:39
【问题描述】:

查询 A 在微秒内执行:

SELECT t1.id
 FROM (SELECT t0.id AS id FROM t0) AS t1
 WHERE NOT (EXISTS (SELECT 1
        FROM t2
        WHERE t2.ph_id = t1.id AND t2.me_id = 1 AND t2.rt_id = 4))
 LIMIT 20 OFFSET 0

但查询 B 大约需要 25 秒:

SELECT t1.id, count(*) OVER () AS count
 FROM (SELECT t0.id AS id FROM t0) AS t1
 WHERE NOT (EXISTS (SELECT 1
        FROM t2
        WHERE t2.ph_id = t1.id AND t2.me_id = 1 AND t2.rt_id = 4))
 LIMIT 20 OFFSET 0

(区别只是select子句中的一项——一个窗口聚合)​​

EXPLAIN 输出如下,对于 A:

 Limit  (cost=0.00..1.20 rows=20 width=4)
   ->  Nested Loop Anti Join  (cost=0.00..3449.22 rows=57287 width=4)
         Join Filter: (t2.ph_id = t0.id)
         ->  Seq Scan on t0  (cost=0.00..1323.88 rows=57288 width=4)
         ->  Materialize  (cost=0.00..1266.02 rows=1 width=4)
               ->  Seq Scan on t2  (cost=0.00..1266.01 rows=1 width=4)
                     Filter: ((me_id = 1) AND (rt_id = 4))

对于 B:

 Limit  (cost=0.00..1.45 rows=20 width=4)
   ->  WindowAgg  (cost=0.00..4165.31 rows=57287 width=4)
         ->  Nested Loop Anti Join  (cost=0.00..3449.22 rows=57287 width=4)
               Join Filter: (t2.ph_id = t0.id)
               ->  Seq Scan on t0  (cost=0.00..1323.88 rows=57288 width=4)
               ->  Materialize  (cost=0.00..1266.02 rows=1 width=4)
                     ->  Seq Scan on t2  (cost=0.00..1266.01 rows=1 width=4)
                           Filter: ((me_id = 1) AND (rt_id = 4))

为了构建分页 UI,我正在添加窗口聚合以获取 LIMITing 之前的总行数。

【问题讨论】:

    标签: sql performance postgresql window-functions


    【解决方案1】:

    您的原始查询可以这样写:

    SELECT t0.id
    FROM t0
    WHERE NOT EXISTS (SELECT 1
                      FROM t2
                      WHERE t2.ph_id = t1.id AND t2.me_id = 1 AND t2.rt_id = 4
                     )
    LIMIT 20 OFFSET 0;
    

    您没有order by,因此查询可以在为结果集找到结果时开始返回结果。添加窗口函数时:

    SELECT t.0.id, count(*) over ()
    

    现在它正在计算结果集中的行数,因此它必须生成整个结果集。因此,查询不仅要获取前 20 行,还必须生成所有这些行。这需要更多时间。

    【讨论】:

      【解决方案2】:

      您能否检查一下 COUN(*) 需要多长时间以及执行计划是什么样的:

      SELECT count(*)
      FROM (SELECT t0.id AS id FROM t0) AS t1
      WHERE NOT (EXISTS (SELECT 1
              FROM t2
              WHERE t2.ph_id = t1.id AND t2.me_id = 1 AND t2.rt_id = 4))
      

      这可能会让您知道为什么需要更长的时间。

      基本上第一个查询从 t0 只读取 20 条符合条件的第一条记录,而第二个查询必须生成符合条件的完整记录集才能对它们进行计数。

      【讨论】:

        【解决方案3】:

        感谢您提供其他答案,这些答案是正确的,因为计数必须做更多的工作,但我从另一个来源找到了解决方案。统计数据不是最新的。

        运行命令后...:

        ANALYZE;
        

        ... Postgresql 能够选择更合适的查询计划,现在两个查询都运行得非常快。

        【讨论】:

          猜你喜欢
          • 2014-11-04
          • 2017-10-05
          • 1970-01-01
          • 1970-01-01
          • 2021-10-12
          • 2019-01-28
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          相关资源
          最近更新 更多