MySQLの統計情報が性能を左右する

MySQLの統計情報(statistics)が性能を左右する理由

同じSQLなのに、ある日は0.02秒、ある日は30秒。
MySQLを触っていると、この現象に一度は遭遇します。

このとき多くの人は「インデックスが悪い」「SQLが悪い」と考えます。しかし実際には、SQLもインデックスも変わっていないことが多いです。

原因はMySQLの統計情報(statistics)です。
MySQLはデータの中身を見てSQLを実行しているのではなく、「統計情報」を元にどのインデックスを使うかを決めています。
つまり、統計情報がズレると、正しいSQLでも遅くなります。

そしてこの問題は、設計の上手い下手と関係なく、運用中のシステムで必ず発生します。

MySQLはデータを見て実行していない

よく誤解されますが、MySQLはテーブルの中身を見て「一番速い方法」を選んでいるわけではありません。
実際にやっているのは「予測」です。

MySQLはオプティマイザ(optimizer)という仕組みがSQLを解析し、

  • フルスキャンするか
  • インデックスを使うか
  • どのインデックスを使うか
  • 結合順序はどうするか

を決めています。

このときの判断材料が「統計情報」です。

統計情報とは、ざっくり言えば

  • テーブルの行数
  • インデックスの分布
  • 値のばらつき(カーディナリティ)

です。

MySQLはこれを使って「この条件なら10件くらい返るはず」と推測します。
ここが間違うと、実行計画が壊れます。

実行計画が壊れる瞬間

例を見ます。

EXPLAIN
SELECT *
FROM orders
WHERE user_id = 1001;

ここで user_id にインデックスがあれば、本来は index scan になります。

しかし統計情報が狂うと、MySQLはこう判断します。

「user_id=1001 は大量にヒットするだろう」

すると何が起きるか。
インデックスを使わずフルテーブルスキャンになります。

これはMySQLがバカなのではなく、予測が間違っただけです。

なぜ統計情報は狂うのか

統計情報はリアルタイム更新ではありません。
MySQLは毎回全行を調べているわけではないからです。
代わりにサンプリングを行っています。

つまり、

  • INSERTが増える
  • DELETEが増える
  • 偏ったデータが入る

と、統計情報が現実とズレていきます。

典型的なのはログテーブルです。

例えば、

  • 古いデータはほぼ参照されない
  • 最近のデータばかり検索される

この状態になると、統計情報は「平均」を見て判断します。
しかし実際の検索は「偏り」を見ています。
ここで実行計画が壊れます。

ANALYZE TABLE の意味

この問題の解決策が ANALYZE TABLE です。

ANALYZE TABLE orders;

これはインデックスを再構築するコマンドではありません。
統計情報を再計算するコマンドです。

実行すると、MySQLが再びサンプリングを行い、オプティマイザの判断材料を更新します。
その結果、急にSQLが速くなることがあります。

実際の現場では、

「何も変えていないのに急に速くなった」

という現象の正体がこれです。

再起動で速くなる理由

MySQLサーバを再起動したら直った、という話があります。
これも同じ原因です。

InnoDBはテーブル統計をメモリにキャッシュします。
再起動するとキャッシュが初期化され、統計が再計算されます。

つまり「再起動で直る」は、
統計情報が更新されただけです。

根本解決ではありません。

注意:毎日ANALYZEすればよいわけではない

ここでよくある誤解があります。

「じゃあcronで毎日 ANALYZE TABLE を回せばいい」

これは危険です。

理由は2つあります。

  • メタデータロックが発生する
  • 実行計画が変わる

ANALYZE TABLEは軽い処理に見えますが、テーブルにロックがかかります。
書き込みの多い本番環境で実行すると、アプリが待たされることがあります。

さらに厄介なのが実行計画の変化です。

統計情報が更新されると、MySQLは「より正しい」と判断した別のプランを選びます。
しかしそれが必ず速いとは限りません。

実際には

  • 10倍速くなることもある
  • 100倍遅くなることもある

という振れ方をします。

MySQLのパフォーマンス問題の本質

MySQLの性能問題は、SQLチューニングの問題に見えます。
しかし多くの場合、本質は

オプティマイザが誤った前提で判断している

ことです。

つまり、

  • SQLが正しい
  • インデックスもある
  • それでも遅い

このとき疑うべきは統計情報です。

どう向き合うべきか

実務で重要なのは「SQLを書く技術」より、「実行計画を見る習慣」です。

最低限、次は確認します。

EXPLAIN ANALYZE SELECT ...;

これを見ると、

  • なぜ遅いのか
  • どこでフルスキャンになっているのか
  • 予測と実測がズレているか

が分かります。

MySQLは単なるデータ保存ツールではありません。
予測エンジンです。

そして統計情報は、その予測の前提条件です。
SQLの書き方よりも、ここがズレた瞬間に性能は崩れます。

遅いSQLに遭遇したとき、クエリを書き直す前に「MySQLは何を信じて判断したのか」を見る。
それが遠回りに見えて、一番速く解決に辿り着きます。