根本原因の特定から始める最適化戦略
クエリが遅いからといって、必ずしもSQL文に問題があるとは限りません。まずはボトルネックを正確に特定し、適切な対策を講じることが重要です。以下はMySQLチューニングの全体像を示すフローです。
遅延の原因は多岐にわたります:キャッシュの無効化、高負荷によるサーバー不安定、不適切なSQL構文、ミスコンフィグレーション、ハードウェア制約などです。
サーバーステータスの監視
接続数が少なく、明確にクエリが遅い場合は、このステップをスキップして直接SQLの最適化に進んでも構いません。
SHOW STATUS;
出力項目が多いので全件は省略しますが、特に注目すべきはQueries(総クエリ数)、Threads_connected(接続スレッド数)、Threads_running(実行中スレッド数)です。
以下のシェルスクリプトで1秒ごとに状態を記録できます:
#!/bin/bash
while true; do
mysqladmin -uroot -p"your_password" extended-status | \
awk '/Queries/{q=$4} /Threads_connected/{c=$4} /Threads_running/{r=$4} END{print q, c, r}' >> monitor.log
sleep 1
done
記録したデータを加工して、1秒あたりのクエリ数を算出:
awk 'NR==1{prev=$1} {diff=$1-prev; prev=$1; print diff, $2, $3}' monitor.log
得られたデータを可視化し、周期的な負荷増加が見られる場合は、キャッシュの有効期限をランダム化(例:3〜9秒の範囲でランダム値)することでピーク負荷を分散させることができます。
最適化対象SQLの特定
方法1: 実行中のスレッドを確認
SHOW PROCESSLIST;
出力例:
+----+------+-----------+------+---------+------+----------+------------------+
| Id | User | Host | db | Command | Time | State | Info |
+----+------+-----------+------+---------+------+----------+------------------+
| 9 | root | localhost | test | Query | 0 | starting | SHOW PROCESSLIST |
+----+------+-----------+------+---------+------+----------+------------------+
State列の値が以下の場合は要注意:
Converting HEAP to MyISAM— メモリ不足でディスク使用Create tmp table— 一時テーブル作成Copying to tmp table on disk— ディスクへの一時テーブルコピーLocked— ロック待ちSorting result— ソート処理
方法2: スロークエリログの活用
my.cnfに以下を追加:
[mysqld]
slow_query_log = 1
slow_query_log_file = /var/lib/mysql/slow-queries.log
long_query_time = 2
log_queries_not_using_indexes = 1
設定後、MySQLを再起動し、以下のコマンドで設定を確認:
SHOW VARIABLES LIKE 'slow_query%';
SHOW VARIABLES LIKE 'long_query_time';
ログ分析にはmysqldumpslowツールを使用:
# 上位10件の頻出クエリ
mysqldumpslow -s c -t 10 /var/lib/mysql/slow-queries.log
# 実行時間の長い上位5件(LEFT JOIN含む)
mysqldumpslow -s t -t 5 -g "left join" /var/lib/mysql/slow-queries.log
SQL実行計画の分析
EXPLAINによる解析
EXPLAIN SELECT product_name, price FROM products WHERE category_id = 3;
重要なカラムの解説:
- type:
const>eq_ref>ref>range>index>ALL(性能順) - key: 実際に使用されたインデックス
- rows: スキャンされる推定行数(少ないほど良い)
- Extra:
Using filesortやUsing temporaryは要改善
PROFILINGによる詳細分析
SET profiling = 1;
-- 実行したいクエリ
SELECT name, email FROM users WHERE status = 'active';
SHOW PROFILES;
SHOW PROFILE FOR QUERY 1;
各ステップの実行時間を詳細に把握できます。
具体的な最適化手法
クエリ改善
SELECT *を避けて必要なカラムのみ指定- 小規模テーブルを駆動表にする(IN/EXISTSの使い分け)
- サブクエリよりJOINを優先
- 冗長カラムを追加してJOINを減らす
インデックス設計
- WHERE句、JOIN条件、ORDER BY/GROUP BYで使われるカラムに作成
- 更新頻度が高いカラム、NULLが多いカラム、選択性の低いカラムは避ける
- 複合インデックスは左端カラムから使用(「最左一致」原則)
- 文字列検索では前方一致(
'abc%')のみ有効、後方一致('%abc')は無効
テーブル設計
- 最小限のデータ型を使用(TINYINT, SMALLINTなど)
- NOT NULL制約を積極的に適用
- TEXT型は分割を検討
- TIMESTAMPをDATETIMEより優先
- 単一テーブルのカラム数は20以内が目安
テーブル分割戦略
- 垂直分割:アクセス頻度の高いカラムと低いカラムを別テーブルに分離
- 水平分割:ユーザーIDの剰余で複数テーブルに分散
- 読み書き分離:マスター(書き込み専用)+スレーブ(読み込み専用)構成
サーバーパラメータ調整
メモリ関連
sort_buffer_size:ソート用バッファjoin_buffer_size:JOIN用バッファread_buffer_size:フルスキャン用バッファ
ストレージ関連
innodb_log_file_size:トランザクションログサイズinnodb_flush_log_at_trx_commit:コミット時のログフラッシュポリシー(1=安全、2=高速)
その他
max_connections:最大接続数tmp_table_size:メモリ内一時テーブルの最大サイズexpire_logs_days:バイナリログの保持日数
ハードウェア最適化(参考)
- SSD/NVMeストレージの採用
- RAID-10構成の利用
- BIOS設定でCPUパフォーマンスモードを有効化
- メモリ周波数を最大性能モードに設定