「サイトが重い気がするけど原因がわからない」そんな中小企業のWeb担当者へ。MySQLのスロークエリログを使ったDB監視の始め方を実践的に解説します。
こんな悩み、ありませんか?
「サイトのページ読み込みが遅くなった気がするけど、どこが原因かさっぱり分からない」 「サーバーを増強したのに、なぜか改善しない」 「データが増えてきたタイミングから、管理画面の動きがどこかモッサリしてきた」
こうした相談は、弊社にも定期的に寄せられます。調査してみると、原因の約7割がデータベースのクエリにあることが多いです。しかし、MySQLのスロークエリログを一度も確認したことがない、という現場は驚くほど多い。
この記事では、「DBのことはよく分からないけどサイトが遅い」と感じているWeb担当者・エンジニアの方に向けて、スロークエリログの有効化から分析・改善までの実践的な第一歩を解説します。特別なツールは必要ありません。MySQLが動いているサーバーさえあれば、今日から始められます。
なぜ「スロークエリ」が問題になるのか
WebサービスやECサイト、WordPressで構築したコーポレートサイトであれ、裏側ではデータを取得・更新するためにMySQLへのSQLクエリが何十・何百と飛んでいます。大半は一瞬で返ってくるのですが、中には1秒・2秒・それ以上かかるクエリが紛れ込んでいます。これが「スロークエリ(遅いクエリ)」です。
データが少ないうちは気にならないスロークエリも、レコード数が数万・数十万と増えるにつれて露骨に遅くなります。さらに同時アクセスが重なると、遅いクエリが詰まってサーバー全体の応答が悪化するという負の連鎖が起きます。
あるクライアントの事例でいうと、LaravelベースのBtoBシステムで「月次レポート画面だけ20〜30秒かかる」という問題がありました。サーバースペックは十分だったのに、です。スロークエリログを有効にして調べると、JOINが4テーブルにまたがるクエリにインデックスが一切貼られていないという状態でした。インデックスを追加したところ、同じ画面が0.3秒以下で返るようになりました。
このような問題は、ログを「見る習慣」がないと永遠に発見できません。
スロークエリログを有効にする手順
まずはMySQLにスロークエリを記録させましょう。設定は my.cnf(または my.ini)に記述する方法と、実行中のMySQLに直接セットする方法の2種類があります。
# /etc/mysql/my.cnf または /etc/my.cnf に追記
[mysqld]
slow_query_log = 1
slow_query_log_file = /var/log/mysql/slow.log
long_query_time = 1
log_queries_not_using_indexes = 1
設定後は sudo systemctl restart mysql で再起動が必要です。
各パラメータの意味を整理しておきましょう。
slow_query_log: スロークエリログの有効・無効を切り替えますslow_query_log_file: ログの書き出し先ファイルパスを指定しますlong_query_time: 何秒以上かかったクエリを記録するかの閾値。まずは1(1秒)から始めるのが現実的ですlog_queries_not_using_indexes: インデックスを使っていないクエリも記録します。これが意外に重要で、0.5秒未満でも将来の地雷になるクエリを把握できます
設定が有効になったか確認するには、以下のコマンドを実行してください。
SHOW VARIABLES LIKE 'slow_query%';
SHOW VARIABLES LIKE 'long_query_time';
ログを「読める形」に変換する:mysqldumpslow
スロークエリログはそのままでは非常に読みにくいテキストファイルです。MySQLに標準で付属している mysqldumpslow コマンドを使うと、クエリをまとめて集計・ランキング表示できます。
# 実行時間の合計が多い順にトップ10を表示
mysqldumpslow -s t -t 10 /var/log/mysql/slow.log
# 実行回数が多い順に表示
mysqldumpslow -s c -t 10 /var/log/mysql/slow.log
出力の見方はこうです。
Count: 842 Time=3.21s (2703s) Lock=0.00s (0s) Rows=1500.0 (1263000)
SELECT * FROM orders WHERE status = 'N' AND created_at > 'S'
Count: このクエリが何回実行されたか(842回)Time=3.21s (2703s): 平均3.21秒、合計2703秒かかっているRows=1500.0: 1回のクエリで平均1500件のレコードを読んでいる
「Countが多くてTimeも長い」クエリが最優先の改善対象です。このケースでは orders テーブルの status と created_at にインデックスがなく、毎回フルスキャンしていることが疑われます。
EXPLAINで原因を特定する
問題のあるクエリが見つかったら、EXPLAIN で実行計画を確認します。
EXPLAIN SELECT * FROM orders WHERE status = 'pending' AND created_at > '2024-01-01';
出力の type カラムが ALL になっていたら、それはフルテーブルスキャンのサインです。改善するにはインデックスを追加します。
-- 複合インデックスを追加
ALTER TABLE orders ADD INDEX idx_status_created (status, created_at);
-- 追加後、再度EXPLAINで改善を確認
EXPLAIN SELECT * FROM orders WHERE status = 'pending' AND created_at > '2024-01-01';
-- type が 'range' や 'ref' に変わっていればOK
よくある失敗パターンと対処法
❌ 失敗1: long_query_time をいきなり厳しくしすぎる
「0.1秒以上を全部記録したい」と設定すると、ログが爆発的に膨らんでディスクを圧迫することがあります。まずは 1 秒から始め、改善が進んできたら 0.5 秒→ 0.3 秒と徐々に下げていくのが正解です。
❌ 失敗2: SELECT * を放置する
スロークエリログを見ると SELECT * が頻出することがあります。必要なカラムだけ指定するよう修正するだけで、転送データ量が減り体感速度が上がることがあります。Laravelであれば ->select(['id', 'name', 'status']) と明示的に指定する習慣をつけてください。
❌ 失敗3: インデックスを追加しすぎる
「インデックスが少ないなら全部のカラムに追加すればいい」と考えがちですが、インデックスが多すぎると INSERTやUPDATEのたびにインデックスも更新が走るため書き込みが遅くなります。実際にEXPLAINで key として使われているインデックスだけ残し、未使用のものは定期的に整理しましょう。
❌ 失敗4: 本番環境でいきなりALTER TABLEを実行する
大きなテーブルに対してALTER TABLEを実行するとテーブルロックが発生し、サービスが一時的に止まる場合があります。本番適用前には必ずステージング環境でテストし、pt-online-schema-change(Percona Toolkit)の使用も検討してください。
開発・運用でお困りなら
システム開発
設計から運用まで、堅牢なシステムを構築します
※ 通常1営業日以内にご返信します
まとめ:DB監視は「見る」ことから始まる
スロークエリログは、難しいツールを導入しなくてもMySQL標準機能で今すぐ始められます。「なんとなく遅い」の感覚を数値と事実で把握できるようになると、改善の優先度付けも格段に楽になります。
弊社がサポートしてきた案件の中でも、「ログを見始めたら数日で重要な問題が見つかった」というケースは珍しくありません。まずはログを有効にして、1週間分のデータを眺めることから始めてみてください。
とはいえ、「EXPLAINの結果を読んでも何をすべきか分からない」「本番環境でのALTER TABLEが怖い」という場面も出てきます。そのときは一人で抱え込まず、ぜひ弊社にご相談ください。20年以上の実績をもとに、あなたの環境に合った改善策をご提案します。