web-dev-qa-db-ja.com

PL / pgSQL関数の最初の実行で、一般的な計画の代わりに最適な計画を実行できますか?

私はできる限り最善の方法で最適化する必要がある、本当に忙しい機能を持っています。この関数は、レガシーアプリケーションによって1秒間に数回要求される、ネストされたselectステートメントです。

インデックスは用意されていますが、関数の最初の実行後にのみ使用されることに気付きました。問題は、Postgresが大部分のケースで非常に排他的なパラメーターのために一般的な実行計画を作成することですが、それは時にはそれほど良くない場合があります。

最初の実行後にEXPLAIN ANALYZEでテストすると、クエリは非常に高速に実行されますが、アプリセッションは関数を1回だけ呼び出してから終了します。最初の実行で実際に最適化されたプランを使用する必要があります。誰でも手伝ってくれる?

コネクションプーリングを管理するコネクタドライバをいじって、DISCARD TEMPではなくDISCARD ALLを発行することで、キャッシュされたセッションの計画を維持し、パフォーマンスを完全に維持できるようにしましたが、本番環境でそれをしたくない。

私たちはCentOS 6で実行されているPostgres 9.4を使用しています。SQL関数として実行しようとしましたが、役に立ちませんでした。実際には、plpgsql funcよりも高速でした。これが機能コードです:

CREATE OR REPLACE FUNCTION public.ap_keepalive_geteqpid_veiid(
    IN tcbserie bigint,
    IN protocolo integer)
  RETURNS TABLE(eqpid integer, veiid integer, tcbid integer, veiplaca character varying, veiproprietariocliid integer, tcbtppid integer, tcbversao character, veirpmparametro double precision, tcbconfiguracao bigint, tcbevtconfig integer, veibitsalertas integer, sluid integer, harid integer) AS
$BODY$
BEGIN
    RETURN QUERY
    SELECT  teqp.eqpID, 
            teqp.eqpveiID AS veiID, 
            tcb.tcbID, 
            tvei.veiPlaca, 
            tvei.veiProprietariocliID, 
            tcb.tcbtppID, 
            tcb.tcbVersao,
            tvei.veiRPMParametro, 
            COALESCE(COALESCE(NULLIF(tcb.tcbConfiguracao, 0), tcc.clcConfiguracaoBitsVeic), 0) AS tcbConfiguracao,
            COALESCE(tcb.tcbevtConfig, 0) AS tcbevtConfig,
            COALESCE(tvei.veiBitsAlertas, 0) AS veiBitsAlertas,
            COALESCE(tvei.veisluID, 0) AS sluID,
            COALESCE(tcb.tcbharID, 0) AS harID
    FROM TabEquipamento teqp
    INNER JOIN TabPacoteProduto tpp ON teqp.eqptppID = tpp.tppID
    INNER JOIN TabComputadorBordo tcb ON teqp.eqptcbID = tcb.tcbID
    INNER JOIN TabVeiculos tvei ON teqp.eqpveiID = tvei.veiID
    LEFT JOIN TabCliente tcli ON tcli.cliid = tvei.veiProprietariocliID
    LEFT JOIN TabClienteConfig tcc ON tcc.clcCliID = tcli.cliID
    WHERE   tcb.tcbserie = $1
        AND teqp.eqpAtivo = 1
        AND tpp.tppIDProtocolo = $2
        AND tvei.veiBloqueioSinal = 0;

END
$BODY$
  LANGUAGE plpgsql VOLATILE COST 100 ROWS 1;

初回実行時の実行計画:

"Function Scan on ap_keepalive_geteqpid_veiid  (cost=0.25..0.26 rows=1 width=116) (actual time=3.268..3.268 rows=1 loops=1)"
"Planning time: 0.032 ms"
"Execution time: 3.288 ms"

2回目の実行:

"Function Scan on ap_keepalive_geteqpid_veiid  (cost=0.25..0.26 rows=1 width=116) (actual time=0.401..0.402 rows=1 loops=1)"
"Planning time: 0.058 ms"
"Execution time: 0.423 ms"

編集:予期しない結果を伴う関数の自動説明出力が追加されました(少なくとも私にとって)。 auto-explainは、postgresが0.230ミリ秒で目的のプレーンで関数を実行したが、関数自体は4.057ミリ秒かかったと主張しています。これが正確かどうかはわかりません。

< 2015-12-14 18:10:02.314 BRST >LOG:  duration: 0.234 ms  plan:
Query Text: SELECT  teqp.eqpID, 
        teqp.eqpveiID AS veiID, 
        tcb.tcbID, 
        tvei.veiPlaca, 
        tvei.veiProprietariocliID, 
        tcb.tcbtppID, 
        tcb.tcbVersao,
        tvei.veiRPMParametro, 
        COALESCE(COALESCE(NULLIF(tcb.tcbConfiguracao, 0), tcc.clcConfiguracaoBitsVeic), 0) AS tcbConfiguracao,
        COALESCE(tcb.tcbevtConfig, 0) AS tcbevtConfig,
        COALESCE(tvei.veiBitsAlertas, 0) AS veiBitsAlertas,
        COALESCE(tvei.veisluID, 0) AS sluID,
        COALESCE(tcb.tcbharID, 0) AS harID
    FROM TabComputadorBordo tcb
    INNER JOIN TabEquipamento teqp ON teqp.eqptcbID = tcb.tcbID
    INNER JOIN TabPacoteProduto tpp ON teqp.eqptppID = tpp.tppID
    INNER JOIN TabVeiculos tvei ON teqp.eqpveiID = tvei.veiID
    LEFT JOIN TabCliente tcli ON tcli.cliid = tvei.veiProprietariocliID
    LEFT JOIN TabClienteConfig tcc ON tcc.clcCliID = tcli.cliID
    WHERE   tcb.tcbserie = $1
        AND teqp.eqpAtivo = 1
        AND tpp.tppIDProtocolo = $2
        AND tvei.veiBloqueioSinal = 0
Nested Loop Left Join  (cost=1.29..18.65 rows=1 width=75) (actual time=0.226..0.230 rows=1 loops=1)
  Join Filter: (tcc.clccliid = tcli.cliid)
  Rows Removed by Join Filter: 3
  ->  Nested Loop Left Join  (cost=1.29..17.57 rows=1 width=75) (actual time=0.205..0.209 rows=1 loops=1)
        ->  Nested Loop  (cost=1.01..17.26 rows=1 width=71) (actual time=0.200..0.203 rows=1 loops=1)
              ->  Nested Loop  (cost=0.72..16.80 rows=1 width=43) (actual time=0.097..0.098 rows=1 loops=1)
                    ->  Nested Loop  (cost=0.58..16.63 rows=1 width=47) (actual time=0.079..0.080 rows=1 loops=1)
                          ->  Index Scan using ix_tabcomputadorbordo_tcbserie on tabcomputadorbordo tcb  (cost=0.29..8.31 rows=1 width=35) (actual time=0.046..0.046 rows=1 loops=1)
                                Index Cond: (tcbserie = $1)
                          ->  Index Scan using ix_tabequipamento_eqptcbid_eqpativo_eqptppid_eqpveiid on tabequipamento teqp  (cost=0.29..8.31 rows=1 width=16) (actual time=0.030..0.031 rows=1 loops=1)
                                Index Cond: ((eqptcbid = tcb.tcbid) AND (eqpativo = 1))
                    ->  Index Only Scan using ix_tabpacoteproduto_tppidprotocolo on tabpacoteproduto tpp  (cost=0.14..0.16 rows=1 width=4) (actual time=0.015..0.015 rows=1 loops=1)
                          Index Cond: ((tppidprotocolo = $2) AND (tppid = teqp.eqptppid))
                          Heap Fetches: 1
              ->  Index Scan using pk_tabveiculos on tabveiculos tvei  (cost=0.29..0.45 rows=1 width=32) (actual time=0.100..0.101 rows=1 loops=1)
                    Index Cond: (veiid = teqp.eqpveiid)
                    Filter: (veibloqueiosinal = 0)
        ->  Index Only Scan using pk_tabcliente on tabcliente tcli  (cost=0.28..0.30 rows=1 width=4) (actual time=0.004..0.005 rows=1 loops=1)
              Index Cond: (cliid = tvei.veiproprietariocliid)
              Heap Fetches: 1
  ->  Seq Scan on tabclienteconfig tcc  (cost=0.00..1.03 rows=3 width=8) (actual time=0.014..0.015 rows=3 loops=1)
< 2015-12-14 18:10:02.314 BRST >CONTEXTO:  função PL/pgSQL ap_keepalive_geteqpid_veiid(bigint,integer) linha 4 em RETURN QUERY
< 2015-12-14 18:10:02.314 BRST >LOG:  duration: 4.057 ms  plan:
Query Text: SELECT * FROM ap_keepalive_geteqpid_veiid (tcbSerie := 8259492, protocolo:= 422);
5
Pivobispo

メーリングリストpgsql-performanceのメンバーから、セッションでのクエリの最初の実行には時間がかかると予想され、pgpoolerを使用するよう提案されました。たぶんそれが、関数自体が4.057ミリ秒かかったときに、クエリが0.230ミリ秒しかかからなかったと自動説明が言った理由です。

トムレーンが書いた:

基本的に、PGセッションによって実行される最初のいくつかのクエリは、後で実行されるクエリよりも遅くなると予想する必要があります。適切な時間セッションを開いたままにしておくようにアプリケーションを修正できない場合は、接続プーラー(pgpoolerなど)を使用してください。

1
Pivobispo