SQL Server高速化の基本|インデックスと統計情報を押さえる入門
この記事でわかる結論(導入)
インデックスは目的の行へ素早く到達するための仕組みで、統計情報は最適な実行計画を選ぶための材料です。
どちらも性能改善の主役ですが、役割が違うため「片方だけ」を触っても改善が安定しないことがあります。
インデックスは「探し方」を変え、統計情報は「選び方」を変えるので、片方が欠けると最適化の前提が崩れます。
インデックスが整っていても統計が古ければ、最初から間違った道を選んでしまうことがあります。
まずは「道路の有無(インデックス)」と「地図の精度(統計情報)」を切り分けると、SQL Serverの遅さに対する打ち手がぶれにくくなります。
切り分けができると、闇雲にインデックスを増やしたり、統計更新を連打したりする無駄が減ります。
さらに、改善の検証が「どこが変わったのか」を説明できる形になり、再発防止の知識が積み上がります。
つまり、その場しのぎではなく、次のトラブルでも同じ手順で前に進めるようになります。
この記事は仕組みの理解から、運用で安定させるためのメンテナンスの型までを一気通貫で整理します。
読み進める途中で用語が出ても、結論に戻れるように「判断の軸」を繰り返し提示します。
加えて、現場でありがちな「とりあえず再構成」「とりあえず統計更新」を卒業できるよう、最短の切り分け手順も用意します。
最後に、改善をやり切った後の「維持のしかた」まで触れるので、日々の運用が楽になります。
運用が回り始めると、パフォーマンス問題の発見から解決までの時間が短くなります。
遅い原因は「道路」か「地図」か(まずの切り分け)
同じSQLでも速かったり遅かったりするなら、インデックス不足だけでなく統計情報のズレも疑う価値があります。
「昨日まで速かったのに今日は遅い」は、データの増減やパラメータ差で計画が外れたサインになりやすいです。
常に遅いなら、まずは不要な全表走査が起きていないかを確認して、インデックスの効きどころを探します。
常に遅いケースは、検索条件に合う道が存在しないか、道があっても列順が合っていないことが多いです。
一方で時々だけ遅いなら、パラメータやデータ分布の変化で実行計画が外れている可能性を優先的に見ます。
この場合は「計画が変わったのか」「同じ計画のまま条件だけが変わったのか」を見分けるのが近道です。
まずは実行計画を眺めて、推定行数と実行行数の差が大きい場所を探すと、疑うべき方向が見えてきます。
次に、その差が出ている演算子の直前で、どの列の条件が効いているかを確認すると原因を絞りやすいです。
さらに、速い実行と遅い実行で「どこが違うか」を比べると、作業の優先順位をつけやすくなります。
この記事のゴール(理解→運用の型まで)
読み終えると、実行計画を見ながら「統計更新が先か、インデックス見直しが先か」を判断できるようになります。
さらに、定期メンテの順序と頻度を決めるための考え方が手元に残ります。
加えて、改善が効かなかったときに次の打ち手へ進むための「確認ポイント」を持ち帰れます。
最終的には、性能問題を「原因の仮説→検証→恒久対策」に落とし込める状態を目指します。
そして、判断の理由を説明できるようになるので、チーム内の合意形成が早くなります。
説明できる状態になると、夜間対応や引き継ぎの負担が目に見えて減ります。
インデックスの基礎(専用道路の作り方)
インデックスはテーブルの中から必要な行を探す速度を上げるためのデータ構造です。
検索が速くなる代わりに、更新時にインデックスも更新されるため書き込みのコストが増えます。
インデックスは増やせば増やすほど万能ではなく、設計と運用の両方が効きます。
ただし作り過ぎると更新が重くなり、保守も難しくなるので「最小で効かせる」意識が重要です。
最小とは「頻出クエリの主要な絞り込みと結合」をカバーできる範囲を指します。
最小でも、適切なら大半の性能問題を抑えられるので、まずは主導線を整えることが大切です。
インデックス設計は一度で完成しないので、観測しながら小さく育てる意識が現実的です。
観測とは、実行計画やI/Oを見て「使われているか」を確かめることです。
Seek/Scanのイメージと「速くなる条件」
Index Seekは条件に合う範囲へ直接ジャンプする動きで、読み取り量が少なくなりやすいです。
Seekが成立するには、検索条件がインデックスの先頭列から使える形になっていることが多いです。
Index ScanやTable Scanは広い範囲をなめる動きで、行数が多いほどI/Oが増えやすいです。
Scanが選ばれるときは、条件が緩いか、推定上は「なめた方が安い」と判断されていることがあります。
WHERE句の絞り込みが強いほどSeekになりやすく、絞り込みが弱いほどScanになりやすいです。
選択度が低い列だけで絞ると、インデックスがあってもScanが選ばれることがあります。
統計情報が古いと選択度の判断がずれて、Seekにすべき場面でもScanになることがあります。
つまり、インデックスの問題に見えて実は統計の問題というケースが起きます。
JOINや集計で大量の行が必要なときは、Scanが最適になる場合もあるので「Scan=悪」と決めつけないことが大切です。
重要なのはScanかSeekかよりも、読み取り量と待機がどれだけ発生しているかです。
「Scanだけど速い」ケースもあるので、実行時間とI/Oの数字で判断する癖をつけます。
さらに、行の取得だけでなく、並び替えやハッシュ処理で発生するワーク領域の有無も合わせて見ます。
ワーク領域が不足すると、tempdbへのスピルで一気に遅くなることがあります。
クラスタ/非クラスタの違いと使い分け
クラスタ化インデックスはテーブルの物理的な並び順を決める役割を持ちます。
並び順が決まるため、範囲検索や並び替えが効くケースがあります。
非クラスタ化インデックスは別の並び順で検索用の道を追加する仕組みです。
非クラスタは検索の入口になり、必要に応じて元の行へ取りに行く動きが発生します。
主キーが連番で増えるテーブルはクラスタ化の相性がよいことが多いです。
更新が多くてキーがランダムに散る場合は、ページ分割が増えて書き込みが苦しくなることがあります。
読み取り中心の参照テーブルは、検索パターンに合わせて非クラスタを整える効果が出やすいです。
更新中心のテーブルは、読み取りと書き込みの両方のコストを見てバランスを取る必要があります。
クラスタは一つしか持てないので、検索・更新・並び替えのうち「最も外せない軸」を選ぶ意識が役に立ちます。
加えて、クラスタキーが非クラスタの末尾にも保持される点を意識すると、サイズ増を見誤りにくくなります。
サイズ増はメモリ消費にも直結するので、結果としてスループットに効きます。
設計の最小セット(WHERE列順・カバリングの考え方)
最初に見るべきは頻出クエリのWHERE句とJOIN条件で、そこに出る列がインデックス候補です。
候補を列挙したら、最も多いアクセスパターンから順に「先頭列」を検討します。
複合インデックスは先頭列の選び方で効きが大きく変わるので、より絞り込める列を先頭に置くのが基本です。
ただし先頭列は「絞り込み」だけでなく「結合」や「並び替え」の要件も加味して決めます。
SELECTに必要な列まで含めるとキー参照が減り、カバリングでI/Oを抑えられることがあります。
キー参照が多いときは、必要列を含めるだけで体感が変わることがあります。
ただし含める列が増え過ぎるとサイズが膨らみ、メモリや更新コストに跳ね返ります。
含める列は「頻出で必要な列」に絞り、たまにしか使わない列は割り切るのが現実的です。
同じ列でも「条件に使う列」と「表示に必要な列」を分けて考えると、過剰なカバリングを避けやすいです。
さらに、集計や並び替えが多いクエリでは、キー順が結果のソート負荷に影響することも意識します。
キー順が合うと、ソートそのものを省略できる場合があります。
よくある落とし穴(過剰・不足・更新コスト)
似たインデックスを重ねると更新が遅くなり、結局どれも使われないことがあります。
重複は検知しづらいので、まずは目的と対象クエリをメモして管理すると事故が減ります。
逆にインデックスが少な過ぎると、軽い絞り込みでもScanが続いてCPUとI/Oが増えます。
作ったら終わりではなく、実際に使われているかを定期的に観察する運用が必要です。
「効果の出たクエリ」と「副作用の出た更新」を両方見て、次の改善に反映します。
観察の単位を「クエリ単発」ではなく「業務のピーク時間帯」に寄せると、現実の負荷に沿った判断ができます。
加えて、ピーク時に遅いクエリがボトルネックになっていないかを確認すると、費用対効果の高い改善が選べます。
ボトルネックが特定できれば、最小の変更で最大の効果を狙えます。
統計情報の基礎(最適ルートを選ぶ判断材料)
統計情報は列の値の分布を要約した情報で、オプティマイザが行数を見積もるために使います。
行数見積もりが実行計画の選択に直結するため、統計情報は性能の再現性に大きく関わります。
見積もりが外れると、選ぶ演算子や結合順が変わり、実行時間が急に伸びることがあります。
特に大規模テーブルでは、わずかな推定ミスがI/Oやメモリの大きな差になります。
統計情報は地味ですが、効いているときほど存在を忘れがちなので、運用の観点で理解しておくと強いです。
統計情報は「正確さ」だけでなく「鮮度」が重要なので、どのタイミングで古くなるかを知ることが実務では効きます。
鮮度の管理ができると、突発的な遅延の発生頻度が下がります。
統計情報とは何か(分布=行数見積もりの材料)
統計情報は「この条件だと何行くらい返るか」を推定するための材料です。
推定は完璧ではないので、まずは「外れやすい場面がある」前提で理解するのが安全です。
推定が小さすぎるとネステッドループが選ばれて、実際は巨大なループになって遅くなることがあります。
推定が小さいとメモリ見積もりも小さくなり、後段でスピルが起きることもあります。
推定が大きすぎるとハッシュ結合が選ばれて、余計なメモリ確保やスピルが起きて遅くなることがあります。
推定が大きすぎると、ソートやハッシュのためのワーク領域が増えてディスクI/Oが増えることもあります。
推定の良し悪しは「計画の形」と「実行時間」の両方に表れるので、どちらも見る癖をつけます。
加えて、同じ計画でもデータ量の増加で見積もり誤差が拡大することがある点も押さえます。
誤差が積み重なると、後段の演算子ほど影響が大きくなることがあります。
統計情報→推定行数→実行計画の流れ
SQL Serverは統計情報を参照して各述語の選択度を推定します。
推定された行数に基づいて、インデックス利用や結合順や演算子の候補が評価されます。
評価の結果として最も安いと見積もった実行計画が選ばれます。
この一連は見積もりに依存するため、統計情報が現実に近いほど計画が安定しやすいです。
計画の安定は、性能の安定と同義ではないので、実行時間も合わせて観察します。
計画が安定していても遅いなら、見積もり以前に「設計が合っていない」可能性もあります。
また、計画が変わっていなくても、I/Oや待機が増えて遅くなることがあるので「計画だけ」で決め打ちしないことも重要です。
判断材料を一つに絞らず、複数の観点で確かめるのが安全です。
自動作成/自動更新の基本と「古いと危ない状況」
自動更新が有効でも、変更が一定量に達しないと更新されないことがあります。
変更量の閾値に達しないまま分布が崩れると、古い推定が温存されます。
大量のINSERTやDELETEやUPDATEが短時間に起きるテーブルは、統計情報がすぐ古くなりやすいです。
特定の値に偏ってデータが増えるテーブルは、分布の変化が推定ズレに直結しやすいです。
月末やキャンペーンなどで分布が急変する業務は、手動更新の方針を検討する余地があります。
日次や週次のバッチ更新があるなら、その直後に統計更新を組み込むと安定することがあります。
更新タイミングを決めるときは、業務ピークと衝突しないかを合わせて考えると運用事故が減ります。
さらに、統計更新がトリガーする計画変更の影響範囲を、重要クエリ単位で把握しておくと安心です。
影響範囲が分かっていると、更新の判断が速くなります。
なぜ統計情報は時々「裏切る」のか(不安定化の代表パターン)
統計情報は万能な真実ではなく、限られた情報からの推定なので外れる場面があります。
外れること自体は異常ではなく、外れたときに早く気づけるかが重要です。
大事なのは裏切りを恐れるよりも、外れやすい条件を知って兆候を見抜くことです。
兆候が見えたら「統計を整える」「計画の再生成を促す」などの方向性が立ちます。
方向性が立てば、対処が試行錯誤から「仮説と検証」に変わるので、改善の速度が上がります。
さらに、方向性が立つと「今やるべきこと」と「後で恒久対策すること」が分けられ、現場の混乱が減ります。
たとえば緊急時は安全な範囲の統計更新を優先し、恒久対策はインデックス設計の見直しに回す判断ができます。
データ偏り・急増で分布が変わる(推定ズレ)
分布が変わっても統計情報が更新されなければ、古い世界観で行数が計算されます。
古い世界観のままだと、最適化の前提がずれるため結合順まで崩れることがあります。
新しい値の領域が増えるとヒストグラムの解像度が足りず、推定が粗くなることがあります。
特定顧客や特定カテゴリにアクセスが集中するシステムは、偏りの影響が出やすいです。
偏りが強いなら、平均的な推定では合わないことがあると理解しておくと楽になります。
偏りが疑わしいなら、統計更新だけでなく「検索条件の組み方」を見直す余地がある点も押さえます。
加えて、偏りが強い列はインデックスの先頭列選定にも影響するので、両輪で考える意識が役に立ちます。
偏りが強いほど、先頭列を誤るとSeekにならず、結果としてI/Oが増えやすいです。
パラメータスニッフィングで計画が合わなくなる
最初の実行で渡されたパラメータに最適化された計画が、その後の別パラメータでも再利用されることがあります。
少量向けの計画が大量データに当たると、ループ回数が爆発して遅くなることがあります。
大量向けの計画が少量データに当たると、不要な重い演算子を選んで無駄が増えます。
時々だけ遅い現象は、パラメータ差でワークロードが変わるケースと相性がよいです。
このタイプは「たまたま遅い」ではなく「条件が揃うと遅い」なので、再現条件を言葉にすると前に進みやすいです。
加えて、再現条件が分かると、統計更新で寄る問題なのか、設計変更が必要な問題なのかの判断がしやすくなります。
判断ができると、無理に手当たり次第の変更をしなくて済みます。
誤推定の“兆候”の見つけ方(推定行数差など)
実行計画で推定行数と実際行数が大きく乖離しているなら、統計情報やパラメータの影響を疑います。
突然Index SeekからScanに変わったなら、条件の選択度が変わったか推定が揺れた可能性があります。
メモリ不足やスピルが出ているなら、推定が外れてメモリ見積もりが崩れた可能性があります。
兆候が出たら、まずは統計更新で推定が寄るかを確認すると切り分けが早いです。
兆候が複数同時に出ているときは、単発の対処ではなく「運用の見直し」が必要なサインです。
また、兆候の変化を時系列で追えるように、問題発生時のメモを残す運用にすると改善が速くなります。
メモが残ると、次回は最初から疑うべき点に当たりを付けられます。
安定したDB運用のためのメンテナンス(両輪を回す型)
安定化のコツは、問題が起きたときだけ触るのではなく、変化を前提に定期的に整えることです。
安定は「最速」よりも「ブレの小ささ」を重視する場面が多いです。
インデックスと統計情報は片方だけ最適化しても不安定になりやすいので、セットで考えます。
セットで考えると、改善の順序が自然に決まり、対処が属人化しにくくなります。
さらに、メンテナンスの目的が明確になり「作業のための作業」を減らせます。
加えて、メンテの実施結果を「効いたかどうか」で評価できるようになり、継続改善が回りやすくなります。
継続改善が回ると、性能問題が起きても落ち着いて対処できます。
まず決める運用方針(頻度・対象・影響)
メンテは頻度を上げるほど安定しやすい一方で、ロックやI/Oで業務に影響することがあります。
影響が読めないときは、まず検証環境や小さな対象で試してから広げます。
まずは重要テーブルと重要クエリを特定して、そこから対象を広げる方が安全です。
更新量の多いテーブルと参照中心のテーブルでは、同じ方針を当てはめない方が無理が出にくいです。
ログ増加やバックアップ窓なども含めて、運用制約を先に洗い出すと設計が楽になります。
方針を決めたら、実施後に何を見て成功とするかもセットで決めると、評価がぶれにくいです。
さらに、失敗したときに「次は何を見るか」も決めておくと、障害対応のスピードが上がります。
成功条件があると、メンテが惰性にならずに済みます。
インデックスメンテ(再構成/再編成の考え方)
断片化が進むと読み取りが増えやすい一方で、ワークロードによっては影響が小さいこともあります。
断片化の数字だけで判断せず、実行時間とI/Oの変化も合わせて見ます。
再編成は比較的軽く、断片化をならす選択肢として使われます。
再構成は効果が大きい一方で重くなりやすいので、対象と時間帯の設計が重要です。
メンテの実施後に統計が更新されるかどうかも、運用手順として意識しておきます。
メンテ後に遅くなった場合の切り戻し手順も用意しておくと、実施判断がしやすいです。
加えて、再構成でI/Oが増える場合は、同時に走るバッチやバックアップとの干渉も確認します。
干渉を避けるだけで、同じ作業でも体感が変わることがあります。
統計情報更新(対象・頻度・実行時の注意)
統計情報更新は推定精度を戻す効果があり、計画の安定化に直結しやすいです。
ただし更新直後に計画が変わることがあるため、影響範囲を理解して実施する必要があります。
大きなテーブルは更新に時間がかかることがあるので、重要度順に対象を絞る運用が現実的です。
更新タイミングを固定すると、障害対応時の切り分けもしやすいです。
更新の粒度を「毎回全体」ではなく「重要テーブル中心」にするだけでも、負荷と効果のバランスが取りやすいです。
さらに、更新後に実行計画がどう変わったかを簡単に確認できる仕組みがあると、安心して回せます。
確認ができれば、統計更新を怖がらずに運用に組み込みやすいです。
推奨の実行順序(統計→確認→必要ならインデックス)
まず統計情報を整えて推定を正し、その上で実行計画が改善するかを確認します。
統計を整えてもなおScanが続くなら、設計としてインデックスが足りない可能性が高まります。
インデックスを追加する前に、既存インデックスの重複や列順の不一致がないかも確認します。
この順序にしておくと、不要なインデックス追加を避けやすいです。
順序を固定すると、チーム内で手順が共有され、夜間対応でも判断が揺れにくいです。
加えて、統計更新で改善した場合でも「なぜ改善したか」をメモしておくと、次回の切り分けが早くなります。
メモは短くてもよいので、残す習慣をつけることが効果的です。
施策の比較表(運用判断の助け)
|施策|目的|メリット|注意点|
|—|—|—|—|
|インデックス再編成|断片化の緩和|比較的軽い|効果が限定的な場合がある|
|インデックス再構成|断片化の解消と再編成|効果が大きい|時間とI/O負荷が大きい|
|統計情報更新|推定精度の回復|計画が安定しやすい|更新後に計画が変わることがある|
|インデックス見直し|検索パターンへの適合|長期的に効きやすい|更新コストやサイズ増に注意|
|運用の見直し|再発防止と属人化低減|判断が速くなる|ルール作りと継続が必要|
遅いときの切り分け手順(最短チェックリスト)
トラブル時は作業を増やすよりも、最短の順で当たりを付ける方が復旧が早いです。
最短とは「一度の確認で次の一手が決まる」順序を指します。
手順を決めておくと、経験が浅いメンバーでも一定の品質で切り分けられます。
加えて、手順があると焦りによる「余計な作業」が減り、リスクのある操作を避けやすいです。
手順はチェックリストとして残しておくと、誰が見ても同じ動きができます。
最初に見るもの(実行計画/推定行数差/IOの当たり)
最初に実行計画を確認して、Scanの有無と推定行数差の大きさを見ます。
次に実行時間の内訳としてI/Oが支配的かCPUが支配的かをざっくり把握します。
待機が支配的なら、クエリだけでなく同時実行やI/O基盤の影響も視野に入れます。
同時実行が絡むときは、改善策がインデックスでも統計でもないことがあると理解します。
まずは「どこで時間が消えているか」を一言で説明できる状態にすると、次の一手が選びやすいです。
さらに、遅い実行と速い実行の差分を比べると、計画差かデータ差かが見えやすいです。
差分が分かれば、触るべき箇所と触らない方がよい箇所が見えてきます。
打ち手の選択(統計更新→改善確認→インデックス見直し)
推定行数差が大きいなら、統計情報を更新して計画と実行時間がどう変わるかを確認します。
改善が弱い場合は、WHERE句とJOIN条件に対してインデックスの列順やカバリングを見直します。
それでも不安定なら、パラメータスニッフィングの可能性を疑って再現条件を整理します。
最終的に「どの条件で遅くなるか」を言語化できると、恒久対策へ繋がります。
恒久対策は一発で決まらないこともあるので、観測→改善→観測のサイクルを短く回す意識が役に立ちます。
加えて、改善前後の実行計画と実行時間を一緒に残すと、次に似た問題が起きたときの再利用ができます。
再利用できる形にしておくと、改善が組織の資産になります。
まとめ(今日からやること)
まず実行計画で推定行数差を確認して、統計更新を先に試す流れを定着させると安定しやすいです。
次に頻出クエリのWHERE句とJOIN条件から最小のインデックス設計を作り、作り過ぎを避けると運用が楽になります。
最後に重要テーブルからメンテの頻度と順序を決め、統計とインデックスを両輪で回すことが性能維持の近道です。
困ったときに戻れるチェック観点を持つと、改善が再現可能になりチームで回しやすくなります。
性能改善は知識よりも手順が強いので、今回の「切り分け→統計→インデックス」の流れをまずは一度実践してみてください。
実践して慣れてきたら、判断基準を小さなルールとして文書化し、運用に組み込むとさらに安定します。
文書化は完璧を目指すより、更新し続けられる形にするのがポイントです。