遅いクエリの発見と改善
「このクエリは遅い。改善せよ」という面接問題に対応する診断チェックリストです。
「遅いクエリの発見と改善」はCoddyKit上の無料Coding Interview Prepレッスンです。 これはレッスン4/4です。 下記で完全なレッスンを無料で読むことができます。その後、ブラウザ内の組み込みコードエディタと24時間対応のAIチューターでハンズオン演習できます。 これはCoding Interview Prep学習パスの一部であり、ウェブとCoddyKitアプリ全体で進捗が同期されます。 Coding Interview Prepコースには全4レッスンが含まれています。
「このクエリは遅い。直してください」という質問
これは面接の総仕上げとなる質問です。面接官が遅いクエリとEXPLAIN ANALYZEの実行計画を提示し、原因の診断を求めます。ここで試されているのは、暗記した小技ではなく方法論です。
優れた回答では、チェックリストを声に出して順番に進めます。測定し、実行計画を読み、支配的なコストを見つけ、仮説を立て、修正案を提案し、検証します。このレッスンでは、そのチェックリストを段階的に組み立てます。
体系的に進め、推論を説明してください。それがシニアレベルの評価を得るためのポイントです。
ステップ1:EXPLAIN ANALYZEで測定する
SQLだけを見て推測してはいけません。EXPLAIN (ANALYZE, BUFFERS)で実際の実行計画を取得します。
ANALYZEは実際の時間と行数を示し、BUFFERSはキャッシュにヒットしているのか、ディスクから読み込んでいるのかを示します。これらを合わせることで、クエリがCPU律速なのか、I/O律速なのか、それとも単に処理量が多すぎるのかが分かります。
数回実行してください。1回目はキャッシュが冷えていることによるペナルティが発生し、計測時間が実態より悪くなる場合があります。
EXPLAIN (ANALYZE, BUFFERS)
SELECT * FROM orders o
JOIN customers c ON c.id = o.customer_id
WHERE o.created_at >= '2026-01-01';ステップ2:支配的なノードを見つける
上から順番に、手当たり次第に読むのはやめましょう。実際に最も時間がかかっているノードを見つけてください。
各ノードの自己時間を計算します。ノード自身の合計actual timeから子ノードの時間を引き、その結果にloopsを掛けます。最も大きな割合を占めるノードが対象です。それ以外はノイズです。
面接では、次のように答えます。実行時間の80パーセントがこの1つのSeq Scanに費やされているため、ここに集中します。それ以外を最適化しても、無駄な労力になります。
ステップ3:見積もりと実測を確認する
支配的なノードで、見積もり行数と実際の行数を比較します。大きな差がある場合、プランナーが正しい情報を得られておらず、悪い実行計画(誤った結合アルゴリズムやアクセス方式)を選んでいる可能性があります。
この例では、行数が1000分の1に過小評価されています。何かを再設計する前に統計情報を更新してください。この1つのコマンドだけで、コストをかけずに実行計画が改善することがよくあります。
ANALYZEはカラム統計情報を再計算します。VACUUM ANALYZEは、不要なタプルの削除と可視性マップの更新も行います。
-- estimate rows=100, actual rows=120000 -> stale stats
ANALYZE orders;
-- or, for bloated tables:
VACUUM ANALYZE orders;よくある原因:インデックス付きカラムに対する関数の使用
最も頻繁に見られる、修正可能なバグは、WHERE句で関数やキャストがカラムを包んでいるケースです。そのためインデックスを使用できず、エンジンがシーケンシャルスキャンを実行します。
この例では、すべての行にDATE()が適用されるため、全件スキャンが強制されます。カラムに関数を適用しない範囲条件(SARGable形式)に書き換えると、created_atのインデックスが使われます。
WHERE lower(email)=...でも考え方は同じです。正規化済みのデータを保存する、カラム自体に対して検索する、または式インデックスを作成する、という方法があります。
-- Not sargable: index unusable
WHERE DATE(created_at) = '2026-01-01'
-- Sargable: range over the bare column
WHERE created_at >= '2026-01-01'
AND created_at < '2026-01-02'よくある原因:インデックスの不足
支配的なノードが、選択性の高いフィルターを持つSeq Scanである場合、またはインデックスのない内側のキーに対してloopsが非常に大きいNested Loopである場合、通常はインデックスが解決策です。
フィルター対象または結合対象のカラムにインデックスを追加します。この例ではcustomer_idにインデックスを作成することで、結合をシーケンシャルスキャンからインデックススキャンに切り替えられます。プランナーは大幅に安い実行計画を選ぶ可能性があります。
EXPLAIN ANALYZEを再実行して確認してください。インデックスが役立ったと決めつけてはいけません。
CREATE INDEX idx_orders_customer
ON orders (customer_id);よくある原因:SELECT *と幅の広い行
SELECT *はすべてのカラムをディスクから読み出し、ネットワーク経由で転送します。また、インデックスがすべてのカラムを含むことはほとんどないため、インデックスオンリースキャンも妨げます。
必要なカラムだけを選択してください。行のサイズが小さくなり、I/Oが減少し、カバリングインデックスによるインデックスオンリースキャンを利用できる場合もあります。
面接官がSELECT *を仕込んでいる場合、そこに気づくことを期待しています。カラム一覧を絞り込むだけで、幅の広いテーブルではすぐに実効性のある改善になることがよくあります。
-- Before
SELECT * FROM orders WHERE customer_id = 42;
-- After: only needed columns (may enable index-only scan)
SELECT order_id, amount FROM orders WHERE customer_id = 42;よくある原因:ディスクへの退避
SortまたはHashノードがディスク使用量(Sort Method: external merge Disk: 25000kBやBatches: > 1)を報告している場合、その処理はwork_memを超過してディスクに退避しています。
対策は、セッション単位でwork_memを増やす、ソートやハッシュに到達する行数を減らす(早い段階でフィルターする)、またはソート済みの順序を提供するインデックスを追加して、ソート自体を不要にすることです。
これは正確な診断であり、シニアレベルの回答として面接官から評価されます。
Sort (actual rows=2000000 loops=1)
Sort Key: o.amount
Sort Method: external merge Disk: 25000kBよくある原因:行の取りすぎ
Rows Removed by Filter: 9500000に注目してください。クエリが1000万行を読み込み、そのほとんどを破棄しています。これは典型的な無駄な処理です。
対策は、アクセス中にフィルターを適用できるようインデックスを追加する(アクセス後に適用するのではなく)、述語の選択性を高める、またはクエリの早い段階でフィルターして、ツリー上位に流れる行を減らすことです。
原則は、処理量を最小限にすることです。できるだけ早く、できるだけ低コストでフィルターしてください。
Seq Scan on events
Filter: (event_type = 'purchase')
Rows Removed by Filter: 9500000診断チェックリスト
面接ではこれを復唱すれば、道を見失うことはありません。
- 測定:
EXPLAIN (ANALYZE, BUFFERS)を使用します。 - 特定:最も時間を消費しているノードを見つけます。
- 比較:見積もり行数と実際の行数を比較し、まず古い統計情報を修正します。
- SARGabilityを確認:フィルター対象のカラムから関数を取り除きます。
- インデックスを追加:選択性の高いフィルターと結合キーにインデックスを設定します。
- カラムを絞り込む:
SELECT *を避けます。 - 監視:ディスクへの退避と行の取りすぎを確認します。
- 検証:実行計画を再実行して確認します。
総仕上げ
完全な例を声に出して説明してみます。実行計画では、5,000万行のordersテーブルに対してSeq Scanが行われ、customer_id = 42でフィルタリングされています。Rows Removed by Filterは5,000万行近くで、推定値は実測値とおおむね一致しています。
診断結果は、選択性の高いフィルターにインデックスがなく、支配的なコストがスキャンであるということです。修正方法はCREATE INDEX ON orders(customer_id)です。再実行すると、実行計画はIndex Scanに切り替わり、実行時間は数秒から1ミリ秒未満に短縮されます。
この「計測・診断・修正・検証」のループが、遅いクエリに関する質問への回答テンプレートです。
CREATE INDEX idx_orders_customer ON orders (customer_id);
EXPLAIN (ANALYZE, BUFFERS)
SELECT order_id, amount FROM orders WHERE customer_id = 42;理解度チェック
クエリがWHERE YEAR(order_date) = 2026でフィルタリングしており、order_dateに既存のB-treeインデックスがあるにもかかわらず、実行計画にはフルSeq Scanが表示されています。最初に行うべき最善の修正は何でしょうか。
まとめ
これで、遅いクエリに関する質問に対応する再現可能な方法を身につけました。
- 必ず
EXPLAIN (ANALYZE, BUFFERS)で計測し、支配的なノードに注目します。 - 推定値と実測値が大きく異なる場合は、まず古い統計情報を修正します。
- 述語をsargableにし、選択性の高いフィルターや結合キーにはインデックスを追加し、
SELECT *を避けます。 - ディスクスピルと過剰取得に対処し、新しい実行計画を検証します。
チェックリストを説明し、具体的な変更を提案し、実行計画を再実行して効果を証明することが、シニアエンジニアとしての回答です。
よくある質問
「遅いクエリの発見と改善」レッスンは無料ですか?
はい。「遅いクエリの発見と改善」の完全なテキストはこのウェブで無料で読めます。インタラクティブに演習し(組み込みコードエディタと24時間対応のAIチューター)、Coding Interview Prepコースの残りをアンロックするには、CoddyKit PROにアップグレードしてください。 Coding Interview Prepコースには全4レッスンが含まれています。
「遅いクエリの発見と改善」で何を学びますか?
「このクエリは遅い。改善せよ」という面接問題に対応する診断チェックリストです。 ブラウザで直接実行するハンズオンコードでCoding Interview Prepを演習し、24時間対応のAIチューターがレッスンを進める中での質問に答えます。
Coding Interview Prepを始めるのに経験は必要ですか?
事前経験は必要ありません。CoddyKitのCoding Interview Prepは初級者から上級者向けに構成されているため、ここから始めるか最初から始めて、自分のペースで進むことができます。 これはレッスン4/4です。
「遅いクエリの発見と改善」レッスンにはどのくらい時間がかかりますか?
ほとんどのCoddyKitレッスンは約5~10分かかります。各レッスンはコンパクトでインタラクティブなので、着実に進歩し、ウェブとアプリ全体で正確に前回の場所から再開できます。
このCoding Interview Prepレッスンでコードを書いて実行できますか?
はい。すべてのCoding Interview Prepレッスンに組み込みコードエディタが含まれているため、ブラウザでリアルコードを書いて実行し、即座のAIフィードバックを取得できます。ローカル設定は不要です。