次のクエリが遅いのはなぜですか(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分以上かかります。
statut
mightヘルプを含む複合インデックス。
_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
を実行しましたか?テーブル統計は引き継がれません。関連:
両方のテーブルに複合インデックスを作成する必要があると思います:(statut, pkid_site)
および(statut, pkid)
オン zones_sites
およびsites_candidats
それぞれ。
または、(pkid_site, statut)
、2つのテーブル間の外部キーを検討します。とは言っても、左/外部ではなく内部結合があるので、最初に定数フィルタリングを行う方が安全な場合があります。外部キーは最初にインデックス全体のスキャンを引き起こす可能性がありますが、定数は最初にその一部のみをスキャンします。
インデックスの変更後、両方のテーブルのstatut
フィールドの既存のインデックスが削除される場合があります。