2015-04-17 28 views
8

我使用的是PostgreSQL 9.2.9,它有以下問題。Postgresql函數執行時間比查詢長得多

有功能:

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毫秒。 爲什麼函數執行的時間比相同的查詢長?謝謝

+0

你能告訴我們兩個執行計劃嗎? (例如,將它們上傳到http://explain.depesz.com) –

+0

我有點簡化了查詢的可讀性。解釋爲函數分析結果:http://explain.depesz.com/s/AfeU和查詢:http://explain.depesz.com/s/OKN除了,我試着解釋函數:http:/ /explain.depesz.com/s/jxnb –

+0

我現在找不到它,但我想我曾經讀過一個函數中的查詢是作爲一個準備好的查詢來運行的,這意味着它的計劃是在參數已知之前做出的導致不好的計劃(但是每次執行該功能時都會節省計劃查詢的開銷)。如果您使用EXECUTE執行查詢作爲動態查詢http://www.postgresql.org/docs/9.2/static/plpgsql-statements.html#PLPGSQL-STATEMENTS-EXECUTING-DYN,則應在參數爲衆所周知。 – Eelke

回答

4

由於「變量」實際上不是變量 - 它們是靜態值(引號中的IE字符串),所以查詢運行速度更快。這意味着執行計劃員可以利用索引。在您的存儲過程中,您的變量是實際變量,規劃人員無法對索引進行假設。例如 - 您可能在requeststatushistory上有部分索引,其中「日期」爲< ='2012-12-31'。該指數只能在知道$ 3的情況下使用。由於它可能持有2015年的日期,所以部分指數將無濟於事。事實上,這將是有害的。

我經常構建我的職務,我串聯我的變量爲文字,然後使用類似執行功能中的字符串以下內容:

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。我用這個「技巧」來取代非常複雜的連接中的子查詢,並且看到了很大的改進。

快樂黑客!

+0

謝謝你的詳細答案,它真的工作得更快! –