Postgresql 函数执行的时间比相同的查询长得多

Posted

技术标签:

【中文标题】Postgresql 函数执行的时间比相同的查询长得多【英文标题】:Postgresql function executed much longer than the same query 【发布时间】:2015-06-24 01:28:36 【问题描述】:

我使用的是 PostgreSQL 9.2.9,遇到以下问题。

有功能:

CREATE OR REPLACE FUNCTION report_children_without_place(text, date, date, integer)
RETURNS TABLE (department_name character varying, kindergarten_name character varying, a1 bigint) AS $BODY$
BEGIN
    RETURN QUERY WITH rh AS (
        SELECT (array_agg(status ORDER BY date DESC))[1] AS status, request
        FROM requeststatushistory
        WHERE date <= $3
        GROUP BY request
    )
    SELECT
        w.name,
        kgn.name,
        COUNT(*)
    FROM kindergarten_request_table_materialized kr
    JOIN rh ON rh.request = kr.id
    JOIN requeststatuses s ON s.id = rh.status AND s.sysname IN ('confirmed', 'need_meet_completion', 'kindergarten_need_meet')
    JOIN workareas kgn ON kr.kindergarten = kgn.id AND kgn.tree <@ CAST($1 AS LTREE) AND kgn.active
    JOIN organizationforms of ON of.id = kgn.organizationform AND of.sysname IN  ('state','municipal','departmental')
    JOIN workareas w ON w.tree @> kgn.tree AND w.active
    JOIN workareatypes mt ON mt.id = w.type AND mt.sysname = 'management'
    WHERE kr.requestyear = $4
    GROUP BY kgn.name, w.name
    ORDER BY w.name, kgn.name;
END
$BODY$ LANGUAGE PLPGSQL STABLE;

EXPLAIN ANALYZE SELECT * FROM report_children_without_place('83.86443.86445', '14-04-2015', '14-04-2015', 2014);

总运行时间:242805.085 毫秒。 但是来自函数体的查询执行得更快:

EXPLAIN ANALYZE WITH rh AS (
SELECT (array_agg(status ORDER BY date DESC))[1] AS status, request
FROM requeststatushistory
WHERE date <= '14-04-2015'
GROUP BY request
)
SELECT
    w.name,
    kgn.name,
    COUNT(*)
FROM kindergarten_request_table_materialized kr
JOIN rh ON rh.request = kr.id
JOIN requeststatuses s ON s.id = rh.status AND s.sysname IN ('confirmed', 'need_meet_completion', 'kindergarten_need_meet')
JOIN workareas kgn ON kr.kindergarten = kgn.id AND kgn.tree <@ CAST('83.86443.86445' AS LTREE) AND kgn.active
JOIN organizationforms of ON of.id = kgn.organizationform AND of.sysname IN  ('state','municipal','departmental')
JOIN workareas w ON w.tree @> kgn.tree AND w.active
JOIN workareatypes mt ON mt.id = w.type AND mt.sysname = 'management'
WHERE kr.requestyear = 2014
GROUP BY kgn.name, w.name
ORDER BY w.name, kgn.name;

总运行时间:2156.740 毫秒。 为什么函数执行的时间比同一个查询要长?谢谢

【问题讨论】:

你能告诉我们两个执行计划吗? (例如上传到explain.depesz.com) 为了提高可读性,我对查询进行了一些简化。解释函数分析结果:explain.depesz.com/s/AfeU 和查询:explain.depesz.com/s/OKN 除此之外,我正在尝试在函数中解释:explain.depesz.com/s/jxnb 我现在找不到它,但我想我曾经读过一个函数中的查询作为准备好的查询运行,这意味着它的计划是在参数已知之前制定的,这可能会导致错误计划(但节省了每次执行函数时计划查询的开销)。如果您要使用 EXECUTE postgresql.org/docs/9.2/static/… 将查询作为动态查询执行,则应在参数已知时制定计划。 【参考方案1】:

您的查询运行得更快,因为“变量”实际上不是变量——它们是静态值(引号中的 IE 字符串)。这意味着执行计划者可以利用索引。在您的存储过程中,您的变量是实际变量,并且规划器不能对索引做出假设。例如 - 您可能在 requeststatushistory 上有一个部分索引,其中“日期”

我经常在我的函数中构造一个字符串,在其中我将变量连接为文字,然后使用以下内容执行该函数:

DECLARE
    my_dynamic_sql TEXT;
BEGIN
    my_dynamic_sql := $$
        SELECT * 
        FROM my_table 
        WHERE $$ || quote_literal($3) || $$::TIMESTAMPTZ BETWEEN start_time
                                                             AND end_time;$$;

    /* You can only see this if client_min_messages = DEBUG */
    RAISE DEBUG '%', my_dynamic_sql; 
    RETURN QUERY EXECUTE my_dynamic_sql;
END;

动态 SQL 非常有用,因为当我有 set client_min_messages=DEBUG; 时,您实际上可以获得查询的解释,我可以从屏幕上抓取查询并将其粘贴回 EXPLAINEXPLAIN ANALYZE 之后,然后看看是什么执行计划者正在做。这还允许您根据需要构建非常不同的查询来优化变量(IE 排除不必要的表,如果有必要)并为您的客户端维护一个通用 API。

您可能会因为担心性能问题而避免使用动态 SQL(我一开始也是如此),但您会惊讶于在计划上花费的时间与在您的数据库上进行几次表扫描的成本相比是多么的少。七表连接!

祝你好运!

跟进:您也可以尝试使用Common Table Expressions (CTE) 来提高性能。如果您有一个信噪比低的表(其中的记录比您实际想要返回的记录多得多),那么 CTE 可能会很有帮助。 PostgreSQL 在查询的早期执行 CTE,并将结果行具体化到内存中。这允许您在查询中的多个位置多次使用相同的结果集。如果设计得当,它的好处真的会令人惊讶。

sql_txt := $$
WITH my_cte as (
   select fk1 as moar_data 1
        , field1
        , field2 /*do not need all other fields taking up RAM!*/
   from my_table
   where field3 between $$ || quote_literal(input_start_ts) || $$::timestamptz
                    and $$ || quote_literal(input_end_ts) || $$::timestamptz
                ),
      keys_cte as ( select key_field
                    from big_look_up_table
                    where look_up_name = ANY($$ || 
                         QUOTE_LITERAL(input_array_of_names) || $$::VARCHAR[])
                  )
SELECT field1, field2, moar_data1, moar_data2
FROM moar_data_table
INNER JOIN my_cte
  USING (moar_data1)
WHERE moar_data_table.moar_data_key in (select key_field from keys_cte) $$;

执行计划很可能表明它选择使用moar_data_tale.moar_data_key 上的索引。这似乎与我在之前的回答中所说的背道而驰——除了keys_cte 结果已实现(因此在竞争条件下不能被另一笔交易更改)——你有自己的小副本用于此查询的数据。

哦,CTE 可以使用在同一个查询中早先声明的其他 CTE。我已经使用这个“技巧”来替换非常复杂的连接中的子查询,并且看到了很大的改进。

黑客愉快!

【讨论】:

以上是关于Postgresql 函数执行的时间比相同的查询长得多的主要内容,如果未能解决你的问题,请参考以下文章

查看时间比查询时间长

使用子查询的查询比使用固定数据而不是子查询的相同查询需要更长的时间

提高 PostgreSQL 函数性能

PostgreSQL:如何在函数中并行运行查询?

PostgreSQL 函数不适用于 WHERE 子句

为大型 Postgresql 表优化嵌套连接窗口函数