web-dev-qa-db-ja.com

PostgreSQLは単純な条件付き結合で非効率的な計画を作成するようです

次の2つのクエリを検討してください。

_SELECT
    t1.id, *
FROM
    t1
INNER JOIN
    t2 ON t1.id = t2.id
    where t1.id > -9223372036513411363;
_

そして:

_SELECT
    t1.id, *
FROM
    t1
INNER JOIN
    t2 ON t1.id = t2.id
    where t1.id > -9223372036513411363 and t2.id > -9223372036513411363;
_

注:_-9223372036513411363_はテーブルの最小値ではなく、条件により結果が減少します(行の総数から、3億5000万)1700万に。

個人的には、_t1.id = t2.id_が自動的に2番目の条件を意味するため、PostgreSQLは両方のクエリに対して同じ計画を立てることを期待しています。しかし残念ながら、PostgreSQLは2つの異なる計画を作成しており、2番目の計画はより優れています。

結合からビューを作成し、ビューのクエリにwhere条件を配置したいので、最初のクエリを強く希望します。そこでは、単一のid列が表示されます(USINGを使用して結合するため、単一のid列が表示されます)。また、3つ以上のテーブルを結合するので、結合ごとにこのような条件を追加しない方がよいでしょう。

この動作には何らかの理由がありますか?それともバグですか?回避策はありますか?

  • _ON t1.id = t2.id_をUSING (id)で置き換えても、両方のクエリで違いはありません。
  • これはPostgreSQL 9.3です
  • 返される行の実際の数は17,658,189です
  • テーブルで分析が実行されました。ただし、PostgreSQLの統計関連の設定はデフォルト値です。
  • 観察:クエリ1の説明は、最終結果の推定は適切ですが、t2のクエリには不十分な計画を使用しています。 2番目のクエリの場合、t1とt2からの行数の見積もりは適切ですが、最終的なマージの見積もりは実際の行数の約半分です。
  • id列は、両方のテーブルの主キーです。テーブルには約350,000,000行あります。 t1は約20GiB、t2は14GiBです。
  • _INNER JOIN_を_LEFT OUTER JOIN_で置き換えると、同様の結果が生成されます
  • より少ない行を選択しても(where条件の最小ID値を増やすことで)、行数が少なくなりすぎて完全に異なるプランを使用するまで、違いはありません。

私が達成しようとしていること

行の多いDBがあり、新しいデータが継続的に挿入されています。このデータには、さまざまな種類のクエリ(さまざまなデータの検索、各列による並べ替え、集計クエリなど)を含むさまざまなレポートを生成する必要があります。

現在の設計では、UPDATE操作はありません。現在、高度に正規化されたデザイン(アンカーモデリングや6NFによって促進されたアイデアに基づく)を実験しています。そのような設計では、JOINとVIEWを広範囲に使用してDBでの作業を快適にするため、これらを効率的に実行できるデータベースが必要です。

(このような問題に基づいて)私が知る限り、PostgreSQLはこのデザイン(約11のテーブルと多数のビュー)に適しているようには見えず、ほとんど常に正規化されていないデザインよりもパフォーマンスが悪いようです1つまたは2つのテーブルがあり、ビューはありません。 JOINクエリを計画する際のこの問題が私のせいだと思っていましたが、まだそうではありません。この問題では、VIEWSを使用することを忘れて、繰り返しの多い条件で詳細なクエリを使用するか、PostgreSQLまたはこのデザインを使用することを忘れるべきです。

テーブル

実際の列数はもう少し多くなりますが、他のテーブルとは関係がないため、この説明とは無関係です。

_CREATE TABLE t1
(
  id bigint NOT NULL DEFAULT nextval('ids_seq'::regclass),
  total integer NOT NULL,
  price integer NOT NULL,
  CONSTRAINT pk_t1 PRIMARY KEY (id)
)

CREATE TABLE t2
(
  id bigint NOT NULL,
  category smallint NOT NULL,
  CONSTRAINT pk_t2 PRIMARY KEY (id),
  CONSTRAINT fk_id FOREIGN KEY (id)
      REFERENCES t1 (id) MATCH SIMPLE
      ON UPDATE NO ACTION ON DELETE NO ACTION
)
_
4
hedayat

次に、それはオプティマイザの死角のように見え、2番目のクエリを使用する必要があります。

2つのテーブルabを結合する条件がある場合:_a.id = b.id_と追加の条件_a.id > @some_constant_は、オプティマイザが「インデックス条件」を使用してa (id)インデックスでインデックススキャンを開始しますが、2番目のインデックスb (id)には使用しません。

したがって、(冗長な)_b.id > @some_constant_を追加すると、b (id)インデックスの一部もスキップして、わずかに効率的なプランを作成できます。

これは、Postgresハッカーグループに改善の提案として投稿することができます(まだ行っていない場合)。


編集後、_FOREIGN KEY_から_t2_の_REFERENCES t1_制約があることがわかります。したがって、クエリを記述する「自然な」(同等の)方法は次のようになります。

_SELECT
    -- whatever
FROM
    t2
  LEFT JOIN
    t1 ON t1.id = t2.id
WHERE t2.id > -9223372036513411363 ;
_

これを試して、作成した実行計画を教えてください。 LEFT(外部)結合にのみ適用され、内部結合には適用されないいくつかの変換があります。
残念ながら、これは別の計画も作成しません。


OPがPostgresパフォーマンスリストに質問を投稿しました。ここでスレッド全体を見ることができます: PostgreSQLは、単純な条件付き結合 で非効率的な計画を作成しているようです。これは考慮された機能ですが、オプティマイザにはまだ実装されていない機能であることを確認します。

はい、残念ながら、実行できることは1つだけです。それは、クエリに両方の条件を含めるだけです。特別な理由がありますか?クエリに_t2.id > ..._条件を書き込むこともできませんか?または、クエリを制御できないソフトウェアによって動的に生成されていますか?

私は個人的にはこの分野での改善を見たいと思っていますし、この問題を修正するパッチ 1 も書いています。この修正を提案するときに私が抱えていた問題は、このプランナーの制限によって影響を受ける人々の数に関する詳細を報告できないことでした。私が提案したパッチは、多くのクエリの計画時間に非常に小さな影響を与え、多くの人が十分な場合に適用しないと、メリットが得られないクエリをスローダウンする価値があると考えられていました。もちろん私はこれに同意します。クエリの計画を遅らせることに関心はありませんが、同時に、この領域での厄介な最適化の悪さを理解しています。

ただし、私が提案したパッチは最初のドラフト案にすぎないことを覚えておいてください。本番用ではありません。

2
ypercubeᵀᴹ