SQLのクエリを構築する際、複数の条件のいずれかに合致するレコードを取得するためにOR演算子を頻繁に使用します。
しかし、大規模なデータベースにおいては、このOR演算子がパフォーマンスのボトルネックになることが少なくありません。
開発の初期段階では問題にならなくても、データ量が数百万件から数千万件に達した際に、突如としてレスポンスが遅延する原因となります。
本記事では、SQLのOR条件がなぜ遅くなるのかという理由から、パフォーマンスを劇的に改善するための具体的な最適化テクニックまでを詳しく解説します。
OR条件がパフォーマンスを低下させる理由
SQLでOR条件を使用すると、データベースの検索効率が著しく低下する場合があります。
その主な理由は、データベースエンジンがインデックスを効率的に活用できなくなるためです。
インデックスの不使用とフルテーブルスキャン
通常、データベースはB-Treeインデックスなどの構造を使用して、目的のデータを高速に検索します。
しかし、複数の異なるカラムに対してOR条件を指定した場合、オプティマイザはそれぞれの条件に合致するデータを一度に特定することが難しくなります。
結果として、インデックスを無視してテーブル全体を一行ずつ確認する「フルテーブルスキャン」が選択されてしまう可能性が高まります。
フルテーブルスキャンが発生すると、データ量に比例して検索時間が増大するため、パフォーマンスが大幅に悪化します。
オプティマイザのコスト計算
データベースのオプティマイザは、クエリを実行する前に最もコストの低い実行計画を計算します。
OR条件が含まれる場合、複数のインデックスをマージして結果を結合する「インデックスマージ」という手法が検討されることもあります。
しかし、インデックスマージの処理自体にオーバーヘッドがかかるため、オプティマイザは「全件スキャンの方が安全である」と判断しがちです。
特に、検索条件が広範囲にわたる場合や、カーディナリティ(データの種類数)が低いカラムでのOR条件は、非効率な実行計画を招く直接的な要因となります。
パフォーマンスを改善する代替手法
OR条件による遅延を回避するためには、クエリの書き方を工夫して、インデックスが確実に効く状態を作り出すことが重要です。
UNION ALL を活用したクエリの分割
OR条件を最適化する最も一般的かつ効果的な手法は、OR条件を複数のクエリに分割し、UNION ALL で結合する方法です。
以下のコード例では、社員テーブルから「営業部に所属している」または「役職がマネージャーである」社員を抽出するケースを考えます。
-- 非効率なクエリ(ORを使用)
SELECT * FROM employees
WHERE department_id = 10 OR job_title = 'Manager';
このクエリでは、department_id と job_title の両方にインデックスがあっても、効率的に使用されないことがあります。
これを UNION ALL を使って書き換えると、以下のようになります。
-- 最適化されたクエリ(UNION ALLを使用)
SELECT * FROM employees WHERE department_id = 10
UNION ALL
SELECT * FROM employees WHERE job_title = 'Manager' AND department_id <> 10;
この書き換えにより、それぞれの SELECT 文が独立したインデックススキャンを実行できるようになります。
なお、UNION を使用すると重複排除のためのソート処理が発生し、パフォーマンスが低下するため、重複がないことが保証されている場合や気にする必要がない場合は必ず UNION ALL を選択してください。
同一カラムに対する IN 演算子の利用
OR条件が同じカラムに対して複数指定されている場合は、IN 演算子を使用することで可読性とパフォーマンスの両面で有利になります。
-- ORを使用した場合
SELECT * FROM products
WHERE category_id = 1 OR category_id = 5 OR category_id = 10;
-- INを使用した場合
SELECT * FROM products
WHERE category_id IN (1, 5, 10);
多くのデータベースエンジンにおいて、IN 演算子は内部的に最適化されやすく、インデックススキャンを効率的に繰り返すことができます。
特に値のリストが静的な場合、オプティマイザはより正確なコスト見積もりを行いやすくなります。
複合インデックスの設計見直し
OR条件で指定するカラムが決まっている場合、適切な複合インデックスを作成することも一つの解決策です。
ただし、OR条件は左右の条件が独立しているため、単純な複合インデックスでは対応できないケースも多いです。
その場合は、それぞれの条件に単一インデックスを付与した上で、先述の UNION ALL を活用するのが定石となります。
具体的な実行プランの比較
実際にクエリがどのように実行されているかを確認するために、EXPLAIN 文を使用して実行プランを分析しましょう。
以下は、OR条件を使用したクエリと UNION ALL に書き換えたクエリの実行プラン(イメージ)の比較です。
| クエリ形式 | スキャンタイプ | パフォーマンス | 主な特徴 |
|---|---|---|---|
| OR条件 | ALL (Full Table Scan) | 低 | 全データを読み込むため、データ量増加に弱い |
| UNION ALL | Index Range Scan | 高 | 各インデックスを個別に活用でき、高速に動作する |
| IN演算子 | Index Range Scan | 高 | 単一カラムの複数条件指定において最も効率的 |
実行プランにおいて type: ALL と表示されている場合は注意が必要です。
type: range や type: ref と表示されるようにクエリを調整することが、最適化のゴールとなります。
2026年現在のモダンなデータベースにおける最適化機能
近年のデータベース製品(PostgreSQL 16以降やMySQL 9系、最新のクラウドデータベースなど)では、OR条件に対する自動最適化機能が進化しています。
例えば、オプティマイザが自動的にクエリを内部で UNION 相当に変換して実行する「OR展開」という機能が強化されています。
しかし、こうした機能は常に万能ではなく、クエリの複雑さや統計情報の鮮度によっては機能しないこともあります。
エンジニアが明示的に最適なクエリ構造を選択することは、2026年においても依然として重要なスキルです。
また、AIアシスタントによるクエリチューニング機能も普及していますが、その提案が正しいかどうかを判断するためには、基礎となる実行原理の理解が不可欠です。
ビットマップインデックスの活用
一部のデータベースでは、OR条件を高速に処理するために「ビットマップインデックス」を活用する手法が有効です。
これは、各条件に合致するレコードをビット列として表現し、ビット演算(OR演算)を行うことで高速に結果をマージする仕組みです。
大量の重複値を持つカラム(例:性別や都道府県)に対して OR条件を使用する場合、ビットマップインデックスの導入を検討する価値があります。
マテリアライズドビューによる事前計算
頻繁に実行される複雑な OR条件クエリについては、マテリアライズドビューを使用して結果を事前に計算しておく手法も有効です。
リアルタイム性が許容される範囲内であれば、クエリ実行時のコストをほぼゼロに抑えることができます。
まとめ
SQLのOR条件は、直感的に記述できる一方で、インデックスを無効化しパフォーマンスを低下させるリスクを孕んでいます。
パフォーマンスが懸念される場合は、UNION ALL への書き換えや IN 演算子の利用を最優先で検討してください。
また、インデックスの設計を見直し、実行プラン(EXPLAIN)を確認する習慣をつけることが重要です。
最新のデータベースエンジンの機能を理解しつつも、原理原則に基づいたクエリチューニングを行うことで、将来的なデータ増加にも耐えうる堅牢なシステムを構築できます。
効率的なSQLを書くことは、サーバーリソースの節約だけでなく、ユーザー体験の向上にも直結する非常に価値のある作業です。
