技術とか戦略とか

IT技術者が技術や戦略について書くブログです。

結合条件で件数が膨らむと性能が悪化する

SQLでのクエリの性能改善というと、「インデックスを設定する」「本当にインデックスが使われているか実行計画を見て確認する」といった、RDBMSの物理的な機能の活用に注目しがち。
 
しかし、それ以前の問題として、「クエリの最中に件数が増加しないようにSQLを書く」という、論理的な観点での工夫により性能改善が図ることができるケースも多いです。特に、性能を考慮せずに業務的な手続きをそのままSQLに書き起こしているような場合は、論理的な観点での性能改善の余地が残っていることが多いです。
 
具体的に言えば、テーブルをJOIN句で結合する際に、結合順や処理順を工夫することで、クエリの最中に件数が増加することを防ぐことができ、性能改善を図ることができます。
そのことを、この記事では説明していきます。
 
----
 
まずは、テーブル結合により件数が膨らむ現象について説明します。
 
これは、件数が膨らまない場合です。


結合元の商品マスタは3件、結合先の商品ページ閲覧履歴は3件、共に商品コードで一意にレコードを特定できます。
以下のようなクエリを発行し、商品名ごとの閲覧回数を取得する場合も、商品マスタの件数と同じ件数の3件が取得されます。
a.商品名 AS 商品名,
b.閲覧回数 AS 閲覧回数
FROM 商品マスタ AS a
JOIN 商品ページ閲覧履歴 AS b
ON a.商品コード = b.商品コード
;
 
次に、結合先のテーブルの件数に引きずられて件数が膨らむ場合です。

結合元の商品マスタは3件、結合先の購入履歴は5件です。
商品マスタは商品コードで一意にレコードを特定できますが、購入履歴は顧客コードと商品コードの組み合わせでないと一意にレコードを特定できません。
以下のようなクエリを発行し、商品名ごとに購入日を取得する場合、結合元の件数である3件を超え、結合先の件数に引きずられ、5件が取得されます。
a.商品名 AS 商品名,
b.購入日 AS 購入日
FROM 商品マスタ AS a
JOIN 購入履歴 AS b
ON a.商品コード = b.商品コード
;
 
そして、直積結合のような形で、結合元や結合先のテーブルの件数を超えて件数が膨らむ場合です。

結合元の商品マスタは3件、結合先のメーカーマスタも3件です。
共にメーカーコードだけでは一意にレコードを特定できず、商品マスタは商品コード、メーカーマスタはメーカーコードと廃止日で一意にレコードを特定します。
以下のようなクエリを発行し、商品名ごとにメーカー名を取得する場合、結合元や結合先の件数である3件を超えて、5件のレコードが取得されます。
a.商品名 AS 商品名,
b.メーカー名 AS メーカー名,
b.廃止日 AS 廃止日
FROM 商品マスタ AS a
JOIN メーカーマスタ AS b
ON a.メーカーコード = b.メーカーコード
;
 
----
 
ここまでの例を見てお気づきかもしれませんが、テーブルのレコードを一意に特定できないカラムを結合条件として指定すると、件数が膨らんでしまいます。
 
テーブルのレコードを一意に特定するには、主キーや一意キーを結合条件として指定するのが間違いありませんが、それ以外のカラムを指定する場合も、設計上もしくは運用上、一意に特定できることもあります。
実際の設計や運用を考慮し、言い換えると実際に入っているデータに着目して、テーブルのレコードを一意に特定できているかどうか調べる方法としては、以下の2つのクエリを投げて結果が一致するかどうかを見る方法があります。一致すればレコードを一意に特定できていますし、一致しなければレコードを一意に特定できていません。乖離が大きければ大きいほど、件数が大きく膨らむ可能性が高まります。
・SELECT COUNT(*) FROM 対象テーブル;
・SELECT COUNT(*) FROM (SELECT DISTINCT 結合条件 FROM 対象テーブル);
 
----
 
このように、結合条件によっては、テーブル結合により件数が膨らむことがあります。
テーブル結合を何度も行うような長いクエリの場合、中間結果の件数が膨らむと、性能が大きく悪化することがあります。
 
それを防ぐために一番良い方法は、結合条件を見直し、件数が膨らまないようなテーブル結合を行うことです。
しかし、業務要件上、それは難しい場合が多いです。
 
結合条件の見直しが難しい場合、以下のような対策により、中間結果の件数の膨らみを最小限にすることが有効になります。
・WHERE句や集計(集合関数やDISTINCT)による件数の絞り込みはなるべく早い段階で行う
・その上で、件数が膨らむテーブル結合はなるべく遅らせる
 
なお、内部結合(JOIN・INNER JOIN)に関しては、単に件数を絞るために記述することもあるので、その場合はExistsやサブクエリに書き換えることで性能が改善する可能性があります。
ただし、これはRDBMSやテーブル定義による所が多く、逆に性能が悪化する可能性も否定できないので、一般化できるものではなく、この方法を使うとしても実行計画を調べてから使うのが望ましいです。