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は何を信じて判断したのか」を見る。
それが遠回りに見えて、一番速く解決に辿り着きます。