web-dev-qa-db-ja.com

Postgresqlサブクエリの速度は個々のクエリよりもはるかに遅い

Postgres 9.1で。特定の郵便番号のタイムゾーンを取得するために1つのクエリを実行しようとしています。データベースにデータをロードし、PostGISを使用して各郵便番号の座標を保存しています。必要なデータを取得するために必要な2つのクエリを次に示します。

SELECT coord FROM public.postal_code WHERE postal_code = 'T1K0T4' LIMIT 1

クエリ時間:6ミリ秒

これにより、postal_code領域の中心の座標がわかります。次に、この座標を使用して、交差するタイムゾーンを見つけます。

SELECT *
FROM public.timezones as tz
WHERE ST_Intersects(ST_GeomFromText('POINT(-112 49)',4326),geom)

クエリ時間:36ミリ秒

2つのクエリを組み合わせると、クエリ時間は7〜8秒にジャンプします。これは私のクエリです:

SELECT *
FROM public.timezones as tz
WHERE ST_Intersects((SELECT coord FROM taduler.postal_code
                     WHERE postal_code = 'T1K0T4' LIMIT 1),geom)

Postal_codeテーブルのcoord列とタイムゾーンテーブルのgeom列に空間インデックスがありますが、サブクエリに使用されていないようです。

誰かがこのクエリを最適化するより良い方法を知っていますか?テーブルの結合など、このクエリのいくつかのバリエーションを試しましたが、すべて同じクエリ速度が得られました。

EXPLAIN ANALYZEからの出力:

Seq Scan on timezones tz  (cost=8.37..167.47 rows=136 width=335547) (actual time=4606.136..7274.428 rows=1 loops=1)
  Filter: st_intersects($0, (geom)::geography)
  InitPlan 1 (returns $0)
    ->  Limit  (cost=0.00..8.37 rows=1 width=128) (actual time=0.011..0.011 rows=1 loops=1)
          ->  Index Scan using postal_code_idx on postal_code  (cost=0.00..8.37 rows=1 width=128) (actual time=0.010..0.010 rows=1 loops=1)
                Index Cond: ((postal_code)::text = 'T1K0T4'::text)
Total runtime: 7274.448 ms

次のクエリのEXPLAIN ANALYZE:

SELECT *
FROM public.timezones as tz
JOIN taduler.postal_code as pc on ST_Intersects(pc.coord, tz.geom)
WHERE pc.postal_code = 'T1K0T4'

出力:

Nested Loop  (cost=0.00..174.61 rows=1 width=335714) (actual time=4870.908..7572.723 rows=1 loops=1)
  Join Filter: ((pc.coord && (tz.geom)::geography) AND (_st_distance(pc.coord, (tz.geom)::geography, 0::double precision, false) < 1e-05::double precision))
  ->  Index Scan using postal_code_idx on postal_code pc  (cost=0.00..8.37 rows=1 width=167) (actual time=0.012..0.019 rows=1 loops=1)
        Index Cond: ((postal_code)::text = 'T1K0T4'::text)
  ->  Seq Scan on timezones tz  (cost=0.00..56.08 rows=408 width=335547) (actual time=0.002..2.795 rows=408 loops=1)
Total runtime: 7572.787 ms
3
Sehael

クエリプランナーの弱点で実行しているようです:最良のインデックスは、テーブルの結合に使用されない場合があります。ここに同様の問題がありました:
最長の接頭辞を見つけるためのアルゴリズム (「text_pattern_opsによる失敗した試行」の章)

Postgres 9.3では、このバージョンを LEFT JOIN LATERAL

SELECT *
FROM  (
    SELECT coord
    FROM   taduler.postal_code
    WHERE  postal_code = 'T1K0T4'
    LIMIT  1
    ) pc
LEFT JOIN LATERAL (
    SELECT *
    FROM   public.timezones tz
    WHERE  ST_Intersects(pc.coord, tz.geom)
   ) tz ON TRUE;

同様の何かがうまくいった この関連する答えでの@ypercubeの解決策
LATERALにはPostgres 9.3以降が必要です。

PostgreSQL 9.1では、最初のクエリをCTEにカプセル化すると役立つかもしれませんが疑わしいです 。 (テストするためのPostGisインストールはここにありません。):

WITH pc AS (
    SELECT coord
    FROM   taduler.postal_code
    WHERE  postal_code = 'T1K0T4'
    LIMIT  1
    )
SELECT *
FROM   pc
JOIN   public.timezones tz ON ST_Intersects(pc.coord, tz.geom);

2つの個別のクエリをカプセル化するplpgsql関数は、確かにうまくいくはずです。

CREATE OR REPLACE FUNCTION f_get_tz(_pc text)
  RETURNS SETOF public.timezones AS
$func$
DECLARE
   _coord geom;
BEGIN

SELECT coord
INTO  _coord
FROM   taduler.postal_code
WHERE  postal_code = _pc
LIMIT  1;

RETURN QUERY
SELECT *
FROM   public.timezones tz
WHERE  ST_Intersects(_coord, tz.geom);

END
$func$  LANGUAGE plpgsql;

コール:

SELECT * FROM f_get_tz('T1K0T4');
3