はじめに
「SQLが遅いな…」と感じたこと、ありませんか?
僕も入社して最初の案件で、SQLの実行が遅くてレビューで何度も指摘されました。
SQLチューニングというと「インデックスを貼ろう」「正規化しよう」といった話になりがちですが、現場ではテーブル構造を変えられない制約も多いです。
そこで今回は、SQLの構造を変えずにできるチューニング方法を紹介します。
特に「ヒント句」という、ちょっと便利な裏技的テクニックに注目していきます。
1. チューニングの基本ステップ
まずは、「SQLが遅い!」となったときに僕が学んだ基本ステップを共有します。
- 遅いSQLを特定する(APMやスロークエリログなど)
- 実行計画を確認する
- ボトルネックを見つける
- 対応策を検討する(構造変更が無理ならヒント句を検討)
この記事では特に「SQLやテーブルを変更できない」ケースに役立つ対策を中心に紹介します。
2. 実行計画を読む(最低限でOK)
実行計画と聞くと難しそうですが、最低限の知識でもチューニングには十分役立ちます。
例:PostgreSQL の EXPLAIN
EXPLAIN SELECT * FROM users WHERE age = 30;
Seq Scan on users (cost=0.00..431.00 rows=5000 width=64)
Filter: (age = 30)
この出力から「インデックス効いてないな」といったことがわかります。
実行計画に出てくる各値の意味
PostgreSQLの実行計画には、以下のような数値情報も含まれています:
項目 | 意味 |
---|---|
cost | クエリプランナーが予測したコスト(開始コスト..終了コスト)で、相対的な重さを表します。単位はPostgreSQL内部独自で、実行時間とは異なります。 |
rows | プランナーが予測する出力行数。実際の実行時とは異なることがあります。 |
width | 出力される1行あたりのバイト数の推定値。 |
これらを参考にして、SQLが「どこで重くなっているのか」を見極めることができます。
よく出る用語と意味(ノード種別)
用語 | 意味 |
---|---|
Seq Scan | テーブル全件をスキャン(遅い) |
Index Scan | インデックスを利用した検索(速い) |
Nested Loop | 小さなデータには強いループ結合方式 |
Hash Join | 大量データの結合に強い方式 |
3. 遅いSQLのよくあるパターンと対策(+ヒント句例)
ここでは、実行計画に現れやすい典型的な「遅いSQL」のパターンと、その原因、ヒント句による具体的な対処方法をまとめます。
✅ ポイント:問題の「傾向」と「それに対して強制すべき実行方法(ヒント句)」を対応させるのが効果的です。
アンチパターン | 実行計画の兆候 | 問題の本質 | ヒント句で強制する内容 |
---|---|---|---|
全件スキャン |
Seq Scan , type: ALL
|
インデックスが使われていない | スキャン方式(IndexScan) |
重いNested Loop |
Nested Loop + 大量データ |
結合順・結合方式が非効率 | 結合方式(HashJoinやMergeJoin) |
後段のFilter |
Filter: が遅延評価される |
条件が早期に使われていない | アクセスパスと結合順(IndexScan, Leading) |
SortやGroup Byが遅い |
Sort , HashAggregate が高コスト |
並び替えや集計がインデックス非活用 | スキャン順序(IndexScan with ORDERED) |
以下で、それぞれの例とヒント句の使い方を詳しく紹介します。
パターン1:全件スキャン
- 原因:インデックスが使われていない
- 実行計画に
Seq Scan
やtype: ALL
が出てくる
ヒント句で改善:
-- Oracle
SELECT /*+ INDEX(users users_age_idx) */ * FROM users WHERE age = 30;
-- MySQL
SELECT * FROM users USE INDEX (users_age_idx) WHERE age = 30;
-- PostgreSQL(pg_hint_plan)
SELECT /*+ IndexScan(users) */ * FROM users WHERE age = 30;
パターン2:大量データ × Nested Loop
- 原因:結合対象が大きく、インデックスも使われていない
ヒント句で改善:
-- Oracle
SELECT /*+ USE_NL(a b) INDEX(b b_fk_idx) */ * FROM a JOIN b ON a.id = b.a_id;
-- MySQL
SELECT STRAIGHT_JOIN * FROM a JOIN b USE INDEX (b_fk_idx) ON a.id = b.a_id;
-- PostgreSQL(pg_hint_plan)
SELECT /*+ NestLoop(a b) Leading(a b) */ * FROM a JOIN b ON a.id = b.a_id;
パターン3:Filterで後から絞る
- 原因:WHEREやJOINの条件がインデックスに効いていない
ヒント句で改善:
-- Oracle
SELECT /*+ INDEX(users users_age_status_idx) */ * FROM users WHERE age = 30 AND status = 'active';
-- MySQL
SELECT * FROM users USE INDEX (users_age_status_idx) WHERE age = 30 AND status = 'active';
-- PostgreSQL(pg_hint_plan)
SELECT /*+ IndexScan(users) */ * FROM users WHERE age = 30 AND status = 'active';
パターン4:Sort や GROUP BY に時間がかかる
- 原因:該当列にインデックスがない、または非効率な集計
ヒント句で改善:
-- Oracle
SELECT /*+ INDEX_ASC(users users_created_at_idx) */ * FROM users ORDER BY created_at;
-- MySQL
SELECT * FROM users USE INDEX (users_created_at_idx) ORDER BY created_at;
-- PostgreSQL(pg_hint_plan)
SELECT /*+ IndexScan(users) Leading(users) */ * FROM users ORDER BY created_at;
4. 各DBでのヒント句の書き方
Oracle
SELECT /*+ INDEX(table index_name) */ ...
MySQL
SELECT * FROM table USE INDEX (index_name) ...
PostgreSQL
PostgreSQLは標準でヒント句がありませんが、セッション変数である程度制御できます。
SET enable_seqscan TO off;
さらに、拡張機能 pg_hint_plan
を導入すればヒント句が使えます:
SELECT /*+ IndexScan(table) */ ...
※ 詳しくは pg_hint_plan GitHub を参照
5. ヒント句の注意点
- DBごとに文法が異なる
- 将来のバージョンで非推奨になる可能性もある
- 不適切な指定で逆に遅くなるケースもある
→ ヒント句は「最終手段」として慎重に使うのがベストです。
6. 実際にどう変わった?(事例)
以下、実際の改善事例(PostgreSQL)です:
-- ヒント句なし
EXPLAIN ANALYZE SELECT * FROM users WHERE age = 30;
-- 実行計画: Seq Scan on users (cost=0.00..431.00 rows=5000)
-- ヒント句あり(pg_hint_plan)
EXPLAIN ANALYZE SELECT /*+ IndexScan(users) */ * FROM users WHERE age = 30;
-- 実行計画: Index Scan using users_age_idx on users
結果:実行時間が大幅に改善(例:800ms → 60ms など)
まとめ
- SQLが遅いときはまず実行計画を確認
- パターンを把握して原因を探る
- 構造変更できないならヒント句で対策
- ヒント句は使い方次第。過信せず慎重に!
僕自身、何度もヒント句に救われてきました。
「構造はいじれないけど、なんとかしたい…」という場面では、きっと役立つはずです。
参考リンク
- SQLパフォーマンス詳説(Zenn/ikeponさん)
- Use the Index, Luke!(日本語訳)
- pg_hint_plan GitHub
- PostgreSQL enable_* 設定一覧
- MySQL USE INDEX ドキュメント
- Oracle SQLヒント一覧
おわりに
最後まで読んでいただきありがとうございました!
この記事は、僕が新人時代に困った経験をもとにまとめました。
「こういうケースも知りたい」「もっと実例が見たい」といったリクエストがあれば、ぜひコメントください!