web-dev-qa-db-ja.com

最適化されていない単一行のPostgres CTEクエリ

私のデータベースから、ユーザーの統計の重み付けされた合計を取得しようとしています。一度に1人または2人のユーザーにのみテーブルをクエリするので、ビューとして作成しました。

ビューであるため、テーブルのすべての行の合計を計算するふりをして、オプティマイザーが単一の行のみを要求している場合にクエリが最適化されることを認識していることを望んでいました。しかし、私のクエリプランは大規模であり、最も内側のポイントで170億行を計算しています。

これがクエリです:

CREATE OR REPLACE VIEW weighted_stats AS
WITH 
    clf AS (SELECT * FROM classifiers order by time_trained desc limit 1),
    weights AS (SELECT kv.key, kv.value from clf, each(clf.weights) AS kv),
    kvs AS (
        SELECT stats.player_id, kv.key, kv.value FROM
        stats, each(stats.hstore_column) AS kv),
SELECT
    stats.player_id,
    SUM(kvs.value :: numeric * weights.value :: numeric) AS stats
FROM
    kvs JOIN weights USING (key)
GROUP BY kvs.player_id;

これはクエリプランです。

explain analyze select * from weighted_stats where player_id=76561197960269296

GroupAggregate  (cost=53645.35..299471.72 rows=1 width=72) (actual time=1014.016..1014.016 rows=0 loops=1)
   Group Key: kvs.id
   CTE clf
     ->  Limit  (cost=20.65..20.65 rows=1 width=84) (actual time=0.017..0.018 rows=1 loops=1)
           ->  Sort  (cost=20.65..22.43 rows=710 width=84) (actual time=0.014..0.014 rows=1 loops=1)
                 Sort Key: classifiers.time_trained
                 Sort Method: quicksort  Memory: 25kB
                 ->  Seq Scan on classifiers  (cost=0.00..17.10 rows=710 width=84) (actual time=0.003..0.005 rows=1 loops=1)
   CTE kvs
     ->  Seq Scan on stats  (cost=0.00..53572.18 rows=10318000 width=722) (actual time=0.037..530.337 rows=336036 loops=1)
   CTE weights
     ->  Nested Loop  (cost=0.00..20.02 rows=1000 width=64) (actual time=0.036..0.046 rows=2 loops=1)
           ->  CTE Scan on clf  (cost=0.00..0.02 rows=1 width=32) (actual time=0.020..0.023 rows=1 loops=1)
           ->  Function Scan on each kv  (cost=0.00..10.00 rows=1000 width=64) (actual time=0.011..0.013 rows=2 loops=1)
   ->  Hash Join  (cost=32.50..241344.73 rows=257950 width=72) (actual time=1014.012..1014.012 rows=0 loops=1)
         Hash Cond: (kvs.key = weights.key)
         ->  CTE Scan on kvs  (cost=0.00..232155.00 rows=51590 width=72) (actual time=0.044..1013.877 rows=62 loops=1)
               Filter: (id = 76561197960269296::bigint)
               Rows Removed by Filter: 335974
         ->  Hash  (cost=20.00..20.00 rows=1000 width=64) (actual time=0.060..0.060 rows=2 loops=1)
               Buckets: 1024  Batches: 1  Memory Usage: 1kB
               ->  CTE Scan on weights  (cost=0.00..20.00 rows=1000 width=64) (actual time=0.040..0.054 rows=2 loops=1)
 Planning time: 0.286 ms
 Execution time: 1017.671 ms

これは私が予想するよりもずっと遅いです。最適化は、グループ化前ではなく、結合前にフィルタリングすることで部分的に機能していますが、kvs CTE(それ自体がフィルタリングされる必要があります)はまだすべてのユーザーに対して計算されているようです。

4

共通テーブル式は、PostgreSQLによって「最適化フェンス」として扱われます。述語がメインクエリからCTEにプッシュダウンされたり、CTE境界を越えて結合が折りたたまれたりすることはありません。代わりに、CTE全体をそのまま評価し、結果を具体化します。メインクエリは、CTEから生成された一時テーブルにアクセスします。

したがって、はい、CTEをサブクエリに変換することで、クエリにメリットがもたらされる可能性があります。

実際のビュー(CREATE VIEWによって作成された)はnotが最適化フェンスとして機能することに注意してください。ビューの定義はそれを使用するクエリに含まれ、通常どおり最適化されます。 CTEの場合、クエリを読みやすくするために「ただ」使用できるように、最適化フェンスの動作をオプションにすることについての議論がありました。ただし、バージョン9.5では、これはまだ実装されていません。

4
chirlu