11.2. 明示的なJOINでプランナを制御する

Postgres7.1から明示的なJOIN構文を使って クエリプランナをある程度制御できるようになりました。どうしてこういうことが 問題になるのか、まずその背景を見る必要があります。

単純な問い合わせ、たとえば

SELECT * FROM a,b,c WHERE a.id = b.id AND b.ref = c.id;
    
では、プランナは自由に与えられたテーブルをいろんな順で結合することが できます。 たとえば、WHERE 節の a.id = b.id を使ってまずAとBを結合し、 他のWHERE節を使ってその結果にCを結合するといったプランを立てることが できます。あるいは、BとCを結合し、その結果にAを結合することもできます。 さらに、AとCを結合し、その結果にBを結合することもできるでしょう。 しかし、それでは効率がよくありません。なぜなら、WHERE節を使って最適化を行う 余地がないためにAとCの全直積が作られるからです。 (Postgresのエキュゼキュータでは、結合はす べて2つのテーブルの間で行われるため、このようにしてひとつひとつ結果を 作っていかなければなりません)。 重要なのは、これらの違った結合の方法は意味的には結果として同じなのですが、 実行コストは大きく違うということです。ですから、プランナはもっとも効率の 良いプランを探すために可能なプランをすべて検査します。

結合の対象がせいぜい2,3個のテーブルなら、心配するほど結合の種類は多 くありません。しかし、テーブルの数が増えると可能な結合の数は指数関 数的に増えていきます。10程度以上にテーブルが増えると、すべての可能 性をしらみつぶしに探索することはもはや実用的ではなくなります。6や7 個のテーブルでさえも、プランを作成する時間が無視できなくなります。 テーブルの数が多すぎるときは、Postgresの プランナはしらみつぶしの探索から、限られた可能性だけを探索する 遺伝的確率的な探索へと切り替わります. (閾値はAdministrator's Guideに記述されている 実行時パラメータのGEQO_THRESHOLDで設定されます)。 遺伝的探索は短い時間で探索を行いますが、必ずしも最適なプランを 見付けるとは限りません。

外部結合が含まれるような問い合わせでは、プランナには通常(内部)結合 よりもずっと選択の余地が小さくなります。たとえば、次のような問い合わせを 考えます。

SELECT * FROM a LEFT JOIN (b JOIN c ON (b.ref = c.id)) ON (a.id = b.id);
    
この問い合わせの検索条件は前述の例と表面的には似ているように思えま すが、BとCの結合結果の行に適合しないAの各行が出力されなければなら ないため、意味的には異なります。したがって、ここではプランナには結 合順に関して選択の余地がありません。まずBとCを結合し、その結果にA を結合しなければならないのです。そういうわけで、この問い合わせでは 計画を立てるのに要する時間は前の例よりも短くなります。

Postgres 7.1では、論理的には内部結合に関 しては制約を設ける必要がないにも関わらず、プランナはすべての明示的 な結合で結合順が制約されるものと見なします。したがって、以下のすべ ての問い合わせは、同じ結果を与えるのにもかかわらず、2番目と3番目の 問い合わせは最初のものよりも短い時間で計画を立てます。

SELECT * FROM a,b,c WHERE a.id = b.id AND b.ref = c.id;
SELECT * FROM a CROSS JOIN b CROSS JOIN c WHERE a.id = b.id AND b.ref = c.id;
SELECT * FROM a JOIN (b JOIN c ON (b.ref = c.id)) ON (a.id = b.id);
    
この効果はたった3つのテーブルでは気にするほどのものではありませんが、 多くのテーブルを結合する際には最後の頼みの綱になるかも知れません。

探索の時間を節約するために、結合順をいつも制約する必要はありません。 なぜなら、普通のFROMリストの結合演算を使うのはOKだからです。たとえば、

SELECT * FROM a CROSS JOIN b, c, d, e WHERE ...;
    
ではプランは他のテーブルと結合する前にAとBを結合しますが、 それ以外については特に制約がありません。この例では、結合順の 候補は5の階乗分の1に減ります。

外部結合と内部結合を含む複雑な問い合わせでは、外部結合の内側で実行 される内部結合の良い結合順をプランが探索するのを妨害しない方が良い かもしれません。結合構文で直接そのように指定することもできますが、 副問い合わせを使って構文的な制約を回避することもできます。たとえば、 次の例です。

SELECT * FROM d LEFT JOIN
        (SELECT * FROM a, b, c WHERE ...) AS ss
        ON (...);
    
ここでは、Dの結合は問い合わせプランの中では最終段階になければなら ないものの、A、B、Cの結合順に関してはプランナは自由に考えることが できます。

このようにしてプランナの探索を制約するのは、プランを立てる時間を節 約するのみならず、プランナが良い問い合わせ計画を立てるように仕向け るためにも役立つテクニックです。もしそのままではプランナが良くない 結合順を選択するようなら、結合構文を使ってプランナがより良い結合順 を選ぶように強制することができます。あなたが、より良い問い合わせ計 画を知っているとしての話ですが。実験してみることをお勧めします。