【问题标题】:Avoid multiple calls on same function when expanding composite result展开复合结果时避免多次调用同一函数
【发布时间】:2015-08-14 16:29:01
【问题描述】:

我有一个重新调整复合结果的 SQL 函数。

CREATE TYPE result_t AS (a int, b int, c int, d numeric);

CREATE OR REPLACE FUNCTION slow_function(int)
RETURNS result_t
AS $$
    -- just some placeholder code to make it slow
    SELECT 0, 0, 0, (
        SELECT sum(ln(i::numeric))
        FROM generate_series(1, $1) i
    )
$$ LANGUAGE sql IMMUTABLE;

在调用函数时,我希望将复合类型的部分展开成几列。当我打电话时效果很好:

SELECT (slow_function(i)).*
FROM generate_series(0, 200) i

a     b     c     d                    
----  ----  ----  -------------------- 
0     0     0     (null)               
0     0     0     0                    
0     0     0     0.6931471805599453   
0     0     0     1.791759469228055
...
Total runtime: 6196.754 ms

不幸的是,这会导致函数被调用每个结果列一次,这会不必要地变慢。这可以通过将运行时间与查询进行比较来测试,该查询直接返回复合结果并且运行速度提高了四倍:

SELECT slow_function(i)
FROM generate_series(0, 200) i
...
Total runtime: 1561.476 ms

示例代码也在http://sqlfiddle.com/#!15/703ba/7

如何在不浪费 CPU 资源的情况下获得包含多列的结果?

【问题讨论】:

  • The FAQ 说可以自言自语,但确实让我感到意外...
  • 值得注意的是(a)pg_sleep 比“慢”函数更适合测试,并且(b)您通常可以通过使用执行 @987654327 的 PL/PgSQL 函数来了解更多信息@.

标签: sql postgresql query-optimization plpgsql


【解决方案1】:

也许你想要一个 LATERAL 子查询。

SELECT t.id, f.* FROM some_table t, LATERAL (SELECT slow_func(t.id)) f

这将为每一行调用一次函数,然后将结果“解包”到输出中的列中。任何子查询都可以用于“展开”,但 LATERAL 可以让您引用其他子句中的列。

我相信 LATERAL 是在 PostgreSQL 9.3 中引入的

【讨论】:

  • -ENOCOFFEE,已删除评论。谢谢欧文。
【解决方案2】:

甚至不需要 CTE。 普通子查询也可以完成这项工作(使用 pg 9.3 测试):

SELECT i, (f).*                     -- decompose here
FROM  (
   SELECT i, (slow_func(i)) AS f    -- do not decompose here
   FROM   generate_series(1, 3) i
   ) sub;

一定不要在子查询中分解函数的复合结果。为外部查询保留它。
当然,需要一个众所周知的类型。不适用于匿名记录。

或者,what @Richard wroteLATERAL JOIN 也可以。语法可以更简单:

SELECT * FROM generate_series(1, 3) i, slow_func(i) f
  • LATERAL 在 Postgres 9.3 或更高版本中隐式应用。
  • 一个函数可以在FROM 子句中独立存在,不必包装在额外的子选择中。想象一下在它的位置有一张桌子。

SQL FiddleEXPLAIN VERBOSE 所有变体的输出。如果发生这种情况,您可以查看函数的多次评估。

COST 设置

一般来说(对于这个特定的查询应该无关紧要),确保对您的函数应用高成本设置,这样规划者就知道避免在必要时更频繁地进行评估。喜欢:

CREATE OR REPLACE FUNCTION slow_function(int)
  RETURNS result_t AS
$func$
    -- expensive body
$func$ LANGUAGE sql IMMUTABLE COST 100000;

Per documentation:

较大的值会导致规划器试图避免过于频繁地评估函数。

【讨论】:

  • 呸,我只是在写那个。感谢您的详细信息。 (删除答案)。随意借用那里的 pl/pgsql 函数,它可以更容易理解测试结果。
  • @CraigRinger:我再次责怪缺乏咖啡。 ;) 可以在 EXPLAIN VERBOSE 输出中看到多个调用,这就是为什么一个简单的 func 在我的小提琴中完成这项工作的原因。
  • 子查询在我的测试用例中仍然很慢,还不知道为什么。但是横向方法效果很好,并且无论如何都更容易阅读。谢谢!
  • @KarlBartel:这很奇怪。我用 pg 9.3 进行了测试,小提琴也展示了效果。确保不要分解子查询中的结果。我在答案中添加了更多内容。
【解决方案3】:

解决此问题的一种方法是使用 WITH 子句。

WITH t AS (
    SELECT slow_function(i) AS s
    FROM generate_series(0, 200) i
)
SELECT (s).*
FROM t

这是可行的,因为 WITH 子句的结果会在查询的其余部分执行之前实现。但这种解决方案通常不适用于更复杂的情况,因为它大大减少了查询优化器以其他方式改进查询执行的选项。所以我还在寻找更好的方法来解决这个问题。

【讨论】:

猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 2020-05-09
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多