SELECT文の実行順序とスロークエリ改善のための最適化手法

データベースのパフォーマンスを最大限に引き出すためには、SQLがどのような順序で解析・実行されるかを理解し、それに基づいたクエリの最適化を行うことが不可欠です。

SQL文の論理的な実行順序

標準的なSQLにおいて、クエリは記述された順序ではなく、以下のステップで解析されます。

  1. FROM: 対象となるデータソースの組み立て
  2. WHERE: 行レベルでのフィルタリング
  3. GROUP BY: データのグループ化
  4. 集約関数: SUM, COUNT, AVGなどの計算
  5. HAVING: グループ化された結果に対するフィルタリング
  6. SELECT: 最終的な出力列の選択と式の計算
  7. 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個程度を目安に整理します。

タグ: SQL database PerformanceTuning QueryOptimization Backend

9月12日 00:56 投稿