目次
SQLを高速化するコツ・テクニック
ここでは、SQLを高速化するための方法について次の内容を解説します。
- キーワードは大文字を使う
- カラムの演算はしない
- サブクエリの引数は「EXISTS述語」を使う
- 「BETWEEN」は基本的に使う必要なし
- EXISTS述語のサブクエリ内では「SELECT *」を使う
- 極値関数(MAX / MIN)でインデックスを活用する
- 行数を数えるときは「COUNT(列名)」を使う
- 「GROUP BY句」でインデックスを活用する
- 「ORDER BY句」でインデックスを活用する
- UNION、INTERSECT、EXCEPT には「ALL」を付ける
- 実はインデックスが使用されていない
- 行ポインタによるアクセスが最速
- ワイルドカードは使わない
- カラム番号は使わない
- テーブルに別名をつける
- 暗黙の型変換は回避する
- IN述語の引数は「最もありそうなキー」を左に記述する
1つずつ見ていきましょう。
キーワードは大文字
SQLのキーワードはすべて大文字で書きましょう!
なぜなら、大文字で書くことでキャッシュを使う確率が上がるからです。
SQLの高速化につながる上に、キーワードを大文字で統一することで検索キーワードなどに指定ができ、デバッグにも役立つでしょう。
--悪い例 select * from テーブル where カラム = 0; --良い例 SELECT * FROM テーブル WHERE カラム = 0;
カラムの演算はしない
カラムの演算は行わないようにしましょう。
なぜなら、インデックスを設定している場合、利用されず処理が遅くなってしまうからです。
--悪い例 SELECT * FROM テーブル WHERE カラム / 3 < 100; --良い例 SELECT * FROM テーブル WHERE カラム < 100;
サブクエリの引数は「EXISTS述語」を使う
たいていの場合、IN述語 と EXISTS述語 は同じ結果を返します。
しかし、高速化の点で考えると EXISTS述語 の方が圧倒的に速く結果を返すのです。
理由としては次の2つあります。
- EXISTSは1つでも条件に合致する行を見つけたらそこで検索を終了するため、INのように全表検索しないから
- インデックスが設定されていればインデックスのみを参照するから
そのため、サブクエリの引数は EXISTS述語 を使うとよいでしょう。
--悪い例 SELECT カラム1 FROM テーブル1 WHERE カラム2 IN (SELECT カラム2 FROM テーブル2); --良い例 SELECT テーブル1.カラム1 FROM テーブル1 WHERE EXISTS (SELECT カラム2.テーブル2 FROM テーブル2 WHERE テーブル1.カラム1 = テーブル2.カラム1);
「BETWEEN」は基本的に使う必要なし
BETWEENは、基本的に使わなくても問題ありません。
なぜなら、「=」「<」「>」などを組み合わせて使えば、同じ条件として記述可能だからです。
しかし、BETWEENを使えるケースでは、使用することでSQL文の可読性の向上や速度改善も望めます。
例えば、BETWEENではなく比較演算子によって2つの式で条件指定した場合、オプティマイザはインデックスに対して複数回操作を行う必要があります。
一方、BETWEENの場合、オプティマイザは指定した範囲のインデックスのノードを1回の操作で比較可能です。
--悪い例 SELECT * FROM テーブル1 WHERE カラム1 BETWEEN 1 and 10000; --良い例 SELECT * FROM テーブル1 WHERE カラム1 >= 1 AND カラム1 < 10000;
【関連記事】
▶︎SQL betweenで範囲指定するサンプルコード 速度アップにもつながる?
EXISTS述語のサブクエリ内では「SELECT *」を使う
EXISTS述語のサブクエリ内には「SELECT *」を使いましょう。
そうすることで、オプティマイザにどのカラムを使うべきか選択させ、カラムにインデックスを設定していればテーブルの走査が必要なくなるからです。
ただし、例外としてカラム名や定数の方が SELECT * よりも高速になることもあります。
--悪い例 EXISTS (SELECT カラム名 FROM …) EXISTS (SELECT 定数 FROM …) --良い例 EXISTS (SELECT * FROM …)
極値関数(MAX / MIN)でインデックスを活用する
極値関数を使うと必ずソートが必要です。
SUM関数やAVG関数などソート領域の使用は、パフォーマンス悪化の原因になりやすい一方、極値関数はソート処理の負荷を軽減可能な場合があります。
それが、「B-Treeインデックス」を作成しているカラムに対して、この極値関数を使用したケースです。
B-Treeインデックスは、作成するときに「キー値」によってソートされ、インデックスのみを走査する低コストな状態で最大値・最小値を取得できます。
行数を数えるときは「COUNT(列名)」を使う
行数を数えるときは「COUNT(列名)」を使いましょう。
その理由も B-Treeインデックス にあります。
COUNT関数の引数に指定するカラムにインデックスが設定されていることで、高速に処理されるのです。
--悪い例 SELECT COUNT(*) FROM テーブル; --良い例 SELECT COUNT(カラム) FROM テーブル;
「GROUP BY句」でインデックスを活用する
GROUP BY句でも、インデックスを有効活用しましょう。
例えば、(カラム1, カラム2, カラム3) にインデックスがある場合、「GROUP BY カラム1」や「GROUP BY カラム1, カラム2」といった集約キーを指定します。
こうすることで、高速なソートが実行されるのです。
ただし、DBMSによってはユニーク・インデックスの場合のみに限られることもあるため注意が必要です。
「ORDER BY句」でインデックスを活用する
ORDER BY句でも、インデックスの活用を勧めています。
理由としては、GROUP BY句と同様です。
高速なソートが期待できるため、SQLの高速化にもつながります。
UNION、INTERSECT、EXCEPT には「ALL」を付ける
UNION、INTERSECT、EXCEPT には「ALL」を付けましょう。
もし、データの重複を気にする必要がない・発生しない場合は、UNION ALL を使うことでソートが発生しません。
ソートが発生すると、重複行を排除するためのコストがかかるため速度低下につながります。
上記の理由から、INTERSECT、EXCEPTも同様のことが言えるのです。
行ポインタによるアクセスは最速
行ポインタによるアクセスは最速です。
なぜなら、どのテーブルでも行ポインタは必ずある擬似カラムであり、格納されている値はレコードの論理アドレスになります。
インデックスも行ポインタを使っているのです。
行ポインタはセッションが終了すると変化する可能性がありますが、同一セッションの間は変わらないため最速のアクセスが保証されます。
ワイルドカードは使わない
ワイルドカードとは、検索などで指定するパターンに使う特殊文字の種類で、いかなる大正文字・文字列にもマッチするもののことを指します。
SQLで言うと、よく「SELECT *」が使われますが、この「*」がワイルドカードにあたります。
ワイルドカードで全カラムを指定すると、SQL実行時に実際に存在するカラム名への読み替えが行われ、オーバヘッド(付加的な処理・負荷)が増えるのです。
さらに、実際には必要ないカラムまで結果として取得するため、余計なメモリを使うことになります。
そのため、ワイルドカードを使わず必要なカラム名だけを指定すると良いでしょう。
カラム名を指定することで、SQLの可読性も上がる・仕様変更にも強くなるといったメリットもあります。
--悪い例 SELECT * FROM テーブル; --良い例 SELECT カラム FROM テーブル;
カラム番号は使わない
カラム番号は使わないようにしましょう。
動的にSQLを生成する場合に重宝しますが、カラム名へ読み替えを行うためパフォーマンス面で問題があります。
--悪い例 SELECT カラム1, カラム2 FROM テーブル ORDER BY 1, 2; --良い例 SELECT カラム1, カラム2 FROM テーブルORDER BY カラム1, カラム2;
テーブルに別名をつける
テーブルには別名をつけるようにしましょう。
なぜなら、テーブルに別名を指定することで、解析する際にどのカラムがどのテーブルに属しているのか判定を省略できるからです。
複数のテーブルを使用するSQLでは特に効果的と言えます。
しかし、単純なSQLであってもSQLを多く実行する場合には、テーブルに別名をつけるのは有効です。
--悪い例 SELECT カラム1, カラム2 FROM テーブル; --良い例 SELECT 別名.カラム1, 別名.カラム2 FROM テーブル (AS) 別名 ;
暗黙の型変換は回避する
SQLで暗黙の型変換は回避するよう考慮しましょう。
多くのデータベースでは、データ型と代入する値が異なっている場合にデータ型を変換します。
その上で、代入ができれば暗黙の型変換を行うのです。
便利な機能ではありますが、暗黙の型変換は1回代入に失敗した上で行なわれるため、オーバーヘッドが発生してしまいます。
また、インデックスも使用されないことになるのです。
--文字型を定義したカラムに対する例文 --悪い例 SELECT カラム FROM テーブル WHERE カラム = 100; --良い例 SELECT カラム FROM テーブル WHERE カラム = '100'; SELECT カラム FROM テーブル WHERE カラム = CAST(100 AS CHAR(100));
IN述語の引数は「最もありそうなキー」を左に記述する
IN述語で引数リストにキーを指定する際には、最もありそうなキーを左に記述しましょう。
なぜなら、IN述語は左から右へ引数を検討し、キーが見つかった時点でTRUEを返し、それ以降の引数は見ないからです。
上記のような、条件判定がTRUEであればそれ以後は見ない方式は「短絡論理和」と言われ、プログラムの高速化としてよく使われます。
SQLでも高速化が見込めるので、ぜひ積極的に使いましょう。
SQLを高速化するためのJOIN句の使い方
JOIN句で結合したテーブルが遅いのですが、どうすれば高速化できますか?
そんな時は、ORDER BYの並び替える項目の指定を変えてみると良いかもしれません
「ORDER BYの指定を変えるだけで高速化が図れるの?」と思うかもしれません。
しかし、指定を変えることでパフォーマンスが1,000倍以上変わることもあるのです。
詳しいやり方については、以下の記事で解説しているので参考にしてみてください。
【関連記事】
▶︎SQL Joinサンプル集 Joinで遅いSQLの原因を調べる方法
また、JOIN句が遅くなってしまう原因や高速化については、以下の記事も参考になると思います。
参考:実例で学ぶ、JOIN (NLJ) が遅くなる理屈と対処法
SQLを高速化するためのサブクエリの使い方
SQL文を書く時にサブクエリ(副問い合わせ)を使うことがあると思います。
もし、「サブクエリって何?」と思っている人がいれば、次の記事を参考にしてみてください!
【関連記事】
▶︎SQLの副問い合わせ(サブクエリ)とは?概要・種類・使い方を解説
サブクエリは「とにかく重く」使いづらいと感じているかもしれません。
サブクエリを使わずに、JOIN句で解決するのであれば JOIN を使うのがベストです。
【関連記事】
▶︎SQLのテーブル結合を理解しよう!JOIN句の種類と使い方とは?
しかし、どうしてもサブクエリを使わなきゃいけない場合もあるでしょう。
サブクエリで高速化を図るためには、先述した内容も含めて次の3つが挙げられます。
- サブクエリの引数は「EXISTS述語」を使う
- EXISTS述語のサブクエリ内では「SELECT *」を使う
- 結合対象であるデータ数をあらかじめ絞り込む
サブクエリの速度が遅くなる原因は、サブクエリのSELECT結果によって仮テーブルが作成されてしまうことです。
そのため、データ数をあらかじめ絞り込み、SELECT結果が有限・現実的な件数になれば速度向上が見込めます。
SQLを高速化するためにインデックスの使い方
SQLではインデックス を作成することで、検索・データ取得動作を高速化することができます。
下記サイトのように、たった1箇所にインデックスをつけただけで、検索速度が6倍になることもあるのです。
参考:「INDEXによる高速化」は本当なのか!?PostgreSQLでパフォーマンスチューニングしてみた
具体的に行ったこととして、次の3つが挙げられています。
- シーケンシャルスキャンになっている箇所を探す
- シーケンシャルスキャンの箇所にインデックスを貼れば速くなる(貼れない場合もある)
- AND条件の絞り込み順序を気にしてみる
つまり、インデックスによる高速化をまとめると次のようになるのです。
また、インデックスの効果を得やすい記述パターンには、大きく分けて次の3つが挙げられます。
- WHERE句での絞り込み
- ORDER BY句での並び替え
- JOINでの結合の条件
詳しくは以下の記事で解説していますので、ぜひ参考にしてみてください!
【関連記事】
▶︎SQLのインデックスとは?作成、確認、削除方法からメリットまで解説!
SQLを高速化するWHEREの使い方はある?
「WHERE句に記述する順番によって速度に影響はあるの?」
こんな風に思う人がいるかもしれません。
オプティマイザはメーカーやDBMS、バージョンによってさまざまあり、一概にWHERE句の順番で速くなる・遅くなるとは言えません。
「速くなるかもしれないし、遅くなるかもしれない」といったところです。
条件の順序で速度が変わる場合には、オプティマイザの方式が関係してきます。
オプティマイザの方式には、大きく分けると「ルール(構文)ベース」と「コストベース」の2つがあります。
それぞれの特徴は次の通りです。
- ルールベース:「=」「<=」「BETWEEN」など、条件の種類によって利用できるインデックスを決定
- コストベース:行数や重複度合いなどの情報をあらかじめ収集し、情報をもとに利用できるインデックスを決定
このうち、ルールベースを適用しているDBMSでは、条件の順序が性能に関係するケースがあります。
ただし、DBMSのどのバージョンを使っているかで変わることもあります。
確実的な要素ではありませんが、順序を意識することで高速化できることもある、くらいに覚えておきましょう。
WHERE句はさまざまな場面で活用されるSQL文です。
例えば、インデックスが設定されていないカラムに対して、ORDER BY句やGROUP BY句でソートすることもあるでしょう。
この時、WHERE句を適切に記述することで、処理対象のデータ量を少なくするのがポイントになります。
なぜなら、そうすることで使用メモリ・CPU量が少なくなり、サーバーにかかる負荷の軽減につながるからです。
このように、WHERE句は直接的に高速化につながることはないかもしれませんが、重要な要素です。
当サイトでも使い方を紹介しているので、ぜひ参考にしてみてください。
【関連記事】
▶︎【SQL】基本だけど重要!条件式の設定”WHERE句”について解説。
まとめ
SQLの高速化についてまとめました。
あなたの知らなかった方法や、今すぐ使えるものもあったかと思います。
ちょっとした記述方法や順番によって、速度が大幅に変わることもあるのです。
ぜひこの記事を参考に、SQLの高速化を試してみてはいかがでしょうか?
・適切な箇所にインデックスを作成する
・絞り込む順番を変えてみる