読者です 読者をやめる 読者になる 読者になる

文系プログラマによるTIPSブログ

文系プログラマ脳の私が開発現場で学んだ事やプログラミングのTIPSをまとめています。

インデックスを貼ればcount(*)は一瞬になるという誤解

mysql

MySQLの話ですが、
インデックス貼ってるからcount(*)は一瞬だぜ!
という事をドヤ顔でいう人がいますが、完全に誤解です。

ストレージエンジンがMyISAMの場合はその通りで、100億レコードあろうと一瞬で件数が返ります。
それもその筈、MyISAMの場合はカウント値が保存されているから高速なのです。
それに対し、InnoDBはカウント値が保存されないので逐次件数取得が必要なので、
高速ではありません。

通常InnoDBでcount(*)とすると、(主に)PKを使った最速の検索法をオプティマイザは選択し、件数取得します。
(主にと書いたのはcount(*)と書いてもどのカラムが選択されるか実は不定のためです)
しかし、いかにメモリ上のデータをカウントするとはいえ、数千万件クラスのカウントは時間がかかるのです。
単純な比較はできませんが、私が以前試したところ、
1億レコードのカウントに30分かかった経験があります。
PKやB-treeインデックスは全てメモリ上に展開されますが、それくらい時間がかかります。

よくあるカウントが低速であることの対策としては、
カウントテーブルを作成してトリガを仕込んでinsert時に自動的にカウント値をインクリメントするという手法があります。
こうすれば物理的なcount(*)は不要ですが、今度はinsertが重くなります。
特に load data で一気にデータを追加する際は注意です。
load dataは高速であるが故、load dataによるinsertと、トリガによる更新が同時に走り、
凄い短時間に大量の更新クエリが走ってディスクに多大な負荷がかかるので注意が必要です。

以前 load data を頻繁に行うテーブルにinsertのトリガを仕込んだらサーバがダウンしてしまった経験があります・・

エキスパートのためのMySQL[運用+管理]トラブルシューティングガイド

エキスパートのためのMySQL[運用+管理]トラブルシューティングガイド