web-dev-qa-db-ja.com

2つの大きなテーブルを結合する単純なクエリの最適化

次のクエリが遅いのはなぜですか(Postgresql 9.6の場合):

SELECT s.pkid AS pkid_site , s.geom AS geom_site , z.pkid AS pkidEmprise, z.geom AS geom_emprise, z.précision_contour
FROM traitements.sites_candidats AS s
JOIN traitements.zones_sites AS z
    ON z.pkid_site = s.pkid
WHERE s.statut = 'selected'
AND z.statut = 'selected';

テーブルの構造は次のとおりです。

CREATE TABLE traitements.sites_candidats (
    pkid serial PRIMARY KEY,
    statut varchar(255) NOT NULL, 
    geom geometry(Point, 2154)
); 
CREATE INDEX ON traitements.sites_candidats (statut);

CREATE TABLE traitements.zones_sites (
    pkid serial PRIMARY KEY,
    pkid_site integer NOT NULL,
    geom geometry(MultiPolygon,2154),
    statut varchar(100)
);

CREATE INDEX zones_sites_idx_pkid_site  ON traitements.zones_sites (pkid_site);
CREATE INDEX zones_sites_idx_statut ON traitements.zones_sites (statut) ;
ALTER TABLE  traitements.zones_sites
    ADD CONSTRAINT zones_sites_references_sites_candidats FOREIGN KEY (pkid_site) REFERENCES traitements.sites_candidats(pkid) ON DELETE CASCADE;

(読みやすさを向上させるために、クエリに含まれていないいくつかの列を削除しました)。

結果として EXPLAIN ANALYZE

Hash Join  (cost=17709.29..152088.29 rows=147368 width=887) (actual time=137.074..879.884 rows=210708 loops=1)
  Hash Cond: (z.pkid_site = s.pkid)
  ->  Seq Scan on zones_sites z  (cost=0.00..85198.14 rows=210717 width=855) (actual time=0.140..384.964 rows=210708 loops=1)
        Filter: ((statut)::text = 'selected'::text)
        Rows Removed by Filter: 23
  ->  Hash  (cost=13433.16..13433.16 rows=210490 width=36) (actual time=136.772..136.772 rows=210708 loops=1)
        Buckets: 65536  Batches: 8  Memory Usage: 2191kB
        ->  Seq Scan on sites_candidats s  (cost=0.00..13433.16 rows=210490 width=36) (actual time=3.085..87.774 rows=210708 loops=1)
              Filter: ((statut)::text = 'selected'::text)
              Rows Removed by Filter: 90265
Planning time: 0.386 ms
Execution time: 888.436 ms

明らかにJOIN .. ONハッシュを行うため、時間がかかります。

テーブル traitements.sites_candidatsには約30万行があります(そのうちの210kはstatut='selected')およびtraitements.zones_sites約210k行(99%近くがstatut = 'selected'を持っています)。クエリには4分以上かかります。

2
Darth Kangooroo

statutmightヘルプを含む複合インデックス。
_statut = 'selected'_のある行のみが対象であるため、部分インデックスがさらに役立つ場合があります。

_-- CREATE INDEX ON traitements.zones_sites (pkid_site)
-- WHERE statut = 'selected';  -- not useful for you!

CREATE INDEX ON traitements.sites_candidats (pkid)
WHERE statut = 'selected';     -- probably also not useful, yet
_

インデックスが小さいほど、書き込み負荷と断片化が少なくなります。

ただし、_zones_sites_のほとんどの行には_statut = 'selected'_があり、そのテーブルから複数の列をフェッチするため、(ほぼ)すべての行、Postgresは最速のオプションとして順次スキャンに固執し、役立つ唯一のインデックスは_sites_candidats_のインデックスです-すべての行の〜70%も対象となります。そのため、上記のインデックスを使用したビットマップインデックススキャンは、おそらく_zones_sites_での別の順次スキャンよりもコストがかかる可能性があります。

パフォーマンスを改善するための残りのオプションは、インデックスのみのスキャンです。関心のある他の唯一の列を_sites_candidats_からインデックスに追加することを検討してください。

_CREATE INDEX ON traitements.sites_candidats (pkid, geom)
WHERE statut = 'selected';
_

インデックスのみのスキャンの前提条件 が満たされている場合にのみ役立ち、テーブルが大きく、追加された列が小さい場合に、より魅力的になります。関連:

最後の質問に対する私の回答のインデックスのみのスキャンの詳細:

あまり期待しないでください、_210708_行を取得するには(EXPLAIN出力に従って)、まだ時間がかかります。それらのすべてが実際に必要ない場合は、LIMIT(_ORDER BY_と組み合わせて)または追加の述部を検討して、フェッチする行数を減らして、はるかに高速にします。

その他:

識別子にアクセント付き文字を使用しません(例:_précision_contour_)。これは機能しますが、エンコードの潜在的な問題が発生します。

statutのデータ型が異なるのはなぜですか? varchar(255)varchar(100)。それらは同じである必要があるようです。少数の状態しか可能でない場合は、 enum またはstatutテーブルを指す小さなFK列を作成して、すべてをより小さく、より高速にします。エラーが発生しにくくなります。

昨日の最後の質問ではPostgres 9.5がありましたが、今日はPostgres 9.6でした。アップグレードしましたか?その場合、アップグレード後にANALYZEを実行しましたか?テーブル統計は引き継がれません。関連:

3

コミュニティwikiの回答

両方のテーブルに複合インデックスを作成する必要があると思います:(statut, pkid_site)および(statut, pkid) オン zones_sitesおよびsites_candidatsそれぞれ。

または、(pkid_site, statut)、2つのテーブル間の外部キーを検討します。とは言っても、左/外部ではなく内部結合があるので、最初に定数フィルタリングを行う方が安全な場合があります。外部キーは最初にインデックス全体のスキャンを引き起こす可能性がありますが、定数は最初にその一部のみをスキャンします。

インデックスの変更後、両方のテーブルのstatutフィールドの既存のインデックスが削除される場合があります。

0
user126897