データベースのパフォーマンスを最大限に引き出すためには、SQLがどのような順序で解析・実行されるかを理解し、それに基づいたクエリの最適化を行うことが不可欠です。
SQL文の論理的な実行順序
標準的なSQLにおいて、クエリは記述された順序ではなく、以下のステップで解析されます。
- FROM: 対象となるデータソースの組み立て
- WHERE: 行レベルでのフィルタリング
- GROUP BY: データのグループ化
- 集約関数: SUM, COUNT, AVGなどの計算
- HAVING: グループ化された結果に対するフィルタリング
- SELECT: 最終的な出力列の選択と式の計算
- ORDER BY: 結果セットのソート
クエリ効率を向上させる基本原則
1. 評価順序を意識したWHERE句の記述
多くのSQLオプティマイザは、WHERE句の条件を下から上(または右から左)に解析します。そのため、より多くのレコードを排除できるフィルタ条件を最後に記述することで、処理対象を早期に絞り込める場合があります。
-- 非効率な例(重いサブクエリが先に評価される可能性がある記述)
SELECT * FROM staff s
WHERE salary > 400000
AND department = 'ENGINEERING'
AND 10 < (SELECT COUNT(*) FROM projects WHERE leader_id = s.id);
-- 効率的な例
SELECT * FROM staff s
WHERE 10 < (SELECT COUNT(*) FROM projects WHERE leader_id = s.id)
AND salary > 400000
AND department = 'ENGINEERING';
2. FROM句におけるテーブルの順序
FROM句に複数のテーブルを指定する場合、パーサは右から左の順で処理を開始する傾向があります。このため、データ件数が最も少ないテーブルを最後に(「駆動表」として)記述することで、結合処理の効率を上げることができます。
-- 履歴テーブル(log_data)がマスターテーブル(config_table)より圧倒的に多い場合
-- 効率的な順序
SELECT COUNT(*) FROM log_data, config_table;
スロークエリを回避するための最適化テクニック
クエリの実行速度を改善し、システム負荷を軽減するための重要なポイントは以下の通りです。
- インデックスの活用: WHERE句やORDER BY句で使用される列には、適切にインデックスを作成します。ただし、カーディナリティ(値の分散度)が低い列(例:性別)へのインデックスは効果が薄い場合があります。
- NULL判定の回避:
IS NULL判定はインデックスの使用を妨げる原因になります。初期値(デフォルト値)を設定し、WHERE col = 0などの形式で検索できるように設計します。 - 否定演算子の抑制:
!=や<>はフルテーブルスキャンの原因となります。可能な限り肯定的な条件に書き換えます。 - OR条件の代替:
ORで条件を結合するとインデックスが効かなくなることがあります。その場合、UNION ALLを使用して個別のクエリ結果を結合することを検討してください。-- 改善前 SELECT id FROM users WHERE status = 1 OR status = 2; -- 改善後 SELECT id FROM users WHERE status = 1 UNION ALL SELECT id FROM users WHERE status = 2; - LIKE演算子の前方一致:
LIKE '%abc%'のような中間一致・後方一致はインデックスが利用できません。高速化が必要な場合は全文検索エンジンを検討するか、前方一致LIKE 'abc%'に留めます。 - 演算や関数の左辺使用禁止: 比較対象の列に対して関数や計算を行わないでください。
-- 不適切な例 SELECT id FROM orders WHERE price / 2 > 5000; -- 適切な例 SELECT id FROM orders WHERE price > 5000 * 2; - EXISTSの利用: 大量のデータを抱えるテーブルとの照合には、
INよりもEXISTSを使用する方がパフォーマンスに優れるケースが多いです。
リソース管理とベストプラクティス
- SELECT * の廃止: 必要なカラムのみを明示的に指定します。ネットワーク帯域の節約とメモリ使用量の削減に直結します。
- データ型の選択: 数値で管理できるデータは文字列型ではなく数値型を採用します。文字列比較よりも数値比較の方が圧倒的に高速です。
- 一時テーブルとテーブル変数の使い分け: 大量のデータを一時的に保持する場合は、インデックスを作成できる一時テーブルを使用します。小規模なデータであればメモリ上のテーブル変数の方が効率的です。
- カーソルの回避: カーソルによる1行ずつの処理は、セットベース(集合論的)なアプローチに比べて低速です。可能な限りJOINやサブクエリによる一括処理に書き換えます。
- インデックスの数: インデックスは検索を速くしますが、INSERTやUPDATEを遅くします。1テーブルあたりのインデックス数は、多くても6個程度を目安に整理します。