前回書いたMySQLパフォーマンスチューニングのためのインデックスの基礎知識に引き続き、MySQLのパフォーマンスチューニングについて学んだことをまとめ。
MySQLを使っていると、クエリが遅い理由をつきとめる必要が出てくる。
どうやって遅いクエリをつきとめ、改善すればよいかについて学んだのでまとめた。
下記のような基礎知識があればパフォーマンスチューニングをうまくやれる、と思う。
クエリ処理の基礎
MySQLがクエリを処理する手順
まずはMySQLがクエリを処理する手順を知っておく必要がある。
処理は以下のような流れで進む。
- クエリキャッシュの中からクエリの結果を探す。見つかればそれを返す。
- クエリを解析して構成要素に分解する。
- クエリの構文が正しいことを確認
- クエリについて基本情報を収集する。
- クエリを基本的な要素に分解した後、何を実行すべきかを判断する。
- クエリオプティマイザが動き始め、最も効率的なクエリの実行方法を見つけ出す。
- クエリを実行する。
クエリキャッシュ
MySQLは、SELECTクエリを実際に分析または実行する前に、クエリキャッシュの中からクエリの結果を見つけようと試みる。MySQLはクエリにハッシュ関数を実行し、キャッシュの中からハッシュ値に該当する結果をまず調べる。キャッシュの中でクエリのハッシュ値が見つかれば、MySQLは期待通りに余分な労力を回避し、キャッシュから結果を取り出してクライアントに送り返す。
クエリキャッシュを使うには、my.cnfでquery_cache_typeに適切な値を与え、query_cache_sizeを設定する。
- query_cache_typeについて
query_cache_typeの値 | 挙動 |
---|---|
0またはOFF | キャッシュを行わない。またはキャッシュした結果の読み出しを行わない。 |
1またはON | SELECT SQL_NO_CACHEで始まるステートメント以外のキャッシュになる。 |
2またはDEMAND | SELECT SQL_CACHEで始まるステートメントのみのキャッシュになる。 |
特定のクエリをキャッシュに入れないようにするためにMySQLに指示することができる。これは、キャッシュの汚染を防止するのに有効。例えば、一日に一度しか実行しないようなクエリの場合、そのクエリをキャッシュに入れても意味はない。そのクエリをキャッシュに入れないようにすれば、一日に何度も繰り返すキャッシュのために容量を確保できる。
SELECT SQL_NO_CACHE * FROM mytable
- query_cache_sizeについて
query_cache_size の値がゼロではない場合は、ストラクチャのアロケートにおよそ 40 KB を必要とするため、クエリ キャッシュのサイズを最低 40 KB でセットする必要がある。
正確なサイズは、システムのアーキテクチャによる。サイズが小さすぎると、警告がでる。
構文解析と分析の最適化
- MySQLは、キャッシュに入っていないクエリに対して目的の処理を実行する前に、クエリを解析して構成要素に分解する。
- クエリの構文が正しいかを確認し、以下の基本情報を収集する。
- どのような種類のクエリか。SELECT、INSERT、UPDATE、DELETEのどれか。あるいはSETやGRANTなどの管理コマンドか。
- どのようなテーブルが関係するか。エイリアスを使用するか。
- どのようなWHERE句を使用するか。
- 他にヒントや修飾子が関係するか
- 要素分解した後、何を実行すべきかを判断する。そしてクエリオプティマイザにより、取得した情報を元に最も効率的なクエリの実行方法(調査すべきレコードの数を限定できる方法)を見つけ出す。
- これは、ディスクI/Oに要する時間が、クエリの所要時間を支配する要因になることが多いためそうしている。
- ※インデックスが役に立つ理由も、この原理で説明できる。
EXPLAINによる調査方法
- テーブル構造は、describeコマンドで表示できる。
describe Hoge;
- MySQLの動作を調べるには、EXPLAINコマンドを使用する。
- ここで得られた情報を元に、インデックスを追加したり、クエリを書き直すことにより、性能を大幅に改善できることがある。
EXPLAIN SELECT questionnaire_records where id=1;
- すると、下記のようなデータが得られる。
+--+-----------+---------------------+-----+-------------+-------+-------+-----+----+-----+ |id|select_type|table |type |possible_keys|key |key_len|ref |rows|Extra| +--+-----------+---------------------+-----+-------------+-------+-------+-----+----+-----+ | 1|SIMPLE |questionnaire_records|const|PRIMARY |PRIMARY| 4|const| 1| | +--+-----------+---------------------+-----+-------------+-------+-------+-----+----+-----+
- 各項目の意味は以下。
カラム名 | 意味 |
---|---|
id | クエリにおけるテーブルのID。EXPLAINは、クエリの各テーブルごとに1つのレコードを出力する。 |
select_type | より大きなクエリの中で、このテーブルがどのような役割をはたすか。 SIMPLE, PRIMARY, UNION, DEPENDENT UNION, SUBSELECT, DERIVEDがある。 |
table | MySQLがレコードを読み出すテーブルの名前 |
type | MySQLは、どのような型の結合を使用するか。あり得る値は、const, system, eq_ref, ref, range, index, ALL。 上記の例では、クエリで定数を使用しているので、typeはconstとなっている。検索条件に値の範囲を指定するとtypeがrangeになる。 |
possible_keys | MySQLがテーブルから行を見つけ出すために使用できるインデックスのリスト。ない場合はNULL。 これがないと、使用できるインデックスがまったく存在しなかったことを意味する。 複数のインデックスがある場合、MySQLは調査すべき行数の少ないインデックスを選択しようとする。 |
key | possible_keysの中から、MySQLが使用すべきだと判断したインデックスの名前。 ここがNULLだとMySQLがインデックスを使用していないということ。 |
key_len | キーの値のサイズ(バイト数) |
ref | キーを照合するときに使用するカラムまたは値 |
rows | クエリを満たすかどうかを調査する必要があるとMySQLが推定した行の数。レコードの追加や削除を頻繁に行うと、推定がずれる。 そういうとき、ANALYZE TABLEを実行すれば、MySQLはインデックス統計情報を更新し、推定精度を改善できる。 |
Extra | このクエリの実行についてMySQLが伝えたい追加情報。 Using whereは、選択するレコードをWHERE句で制限したことを意味する。 Using indexだと、インデックスだけのクエリを実行したことを意味する。 |
同じカラムに対して複数のレコードを取り出す場合、ORとIN()のどちらを使うべきか。数が増えるに連れ、IN()を使用した方が明らかにクエリ文字列は小さくなる。クエリ文字列が小さければ、構文解析のオーバーヘッドが少なくなり、性能の改善につながる。
- クエリの実行
- MySQLはクエリの実行中に以下のような情報を収集する
- クエリを実行したユーザ
- 処理にかかった時間
- 返した行数
- MySQLはクエリの実行中に以下のような情報を収集する
※ログの使用を設定している場合、クエリ時間がサーバの設定値を超えると、サーバは上記の情報をスロークエリログに書き込む。
オプティマイザの機能と特徴
遅いクエリを特定する方法
- 遅いクエリを突き止めるためのもっとも簡単な方法は、MySQLに実際にクエリの処理を行わせ、スロークエリログを出させること。
- スロークエリログとは、一定の秒数よりも長い時間がかかったクエリをログに記録することができる。MySQLは、クエリに加えて、ほかのメタデータ(データに関する情報を記述したデータ)もログに記録する。
- ただし、本当の原因がそのクエリである保障はない。そのときたまたま遅く、次にやってみると速い場合もある。その原因は以下のようなものが考えられる。
- テーブルがロックされていたので、クエリが待機した。Lock_timeはロックが解除されるまで待機していた時間を示す。
- データもインデックスも、まだメモリ内のキャッシュに入っていなかった。MySQLを初めて起動した場合や、適切なチューニングを施していない場合、起こりやすい。
- たまたまバックアップなどが動いていたので、あらゆるディスクI/Oが大幅に遅くなっていた
- 同じ時間に、サーバがほかの何百もの無関係なクエリを処理していたので、効率的に作業を実行できるだけのCPUパワーが足りなかった。
- ログに同じクエリが何度も現れていれば、そのクエリが遅い可能性は高い。
遅いクエリを改善する方法
一般的な方法
- 無駄なクエリを減らす
- 無駄な結合を減らす/結合させるべきものは結合させる
- インデックスを適切に貼る
- クエリキャッシュをうまく利用する
特殊な方法
- 結合順序を指定する。
- EXPLAINクエリを調べて、もしもMySQLが実行した結合よりも最適な結合順序を見つけたとしたら、カンマまたはJOINキーワードの代わりにSTRAIGHT_JOINを使用すればいい。
SELECT * FROM table1 STRAIGHT_JOIN table2 WHERE ...
- インデックスの使用
- MySQLに検討してほしいインデックスのリストを提供し、ほかのインデックスをすべて無視させるには、テーブル名の後にUSE INDEXを追加すればいい。
SELECT * FROM mytable USE INDEX (name, address) ...
-
- MySQLにインデックスを無視させたい場合は、IGNORE INDEXを使用する。
SELECT * FROM mytable IGNORE INDEX (priority) ...
-
- 特定のインデックスを使用することをMySQLに強制するには、FORCE INDEXを使用する
SELECT * FROM mytable FORCE INDEX (name) ...
実際やってみた
表示させるのに10秒近くかかっていたページを、1秒で表示させることができた。
まずはどこが遅いかをスロークエリログなどを利用して突き止め、そのクエリが遅い理由をEXPLAINを使ったり、クエリを見て考えた。
なぜ遅いか仮説を立て、それを解消するようにコードを変更した。ちなみに、主な原因は無駄な情報の取得(結合しなくていいテーブルまで結合していた)があったことと、インデックスのはりかたが不適切なことだった。
原因を追求して問題を見つけて解決するって作業は面白いなぁと思った。
関連記事
- MySQLパフォーマンスチューニングのためのインデックスの基礎知識 - 久保清隆のブログ @ライブレボリューション
- MySQLの基礎(概要編) 〜プログラミング基礎の基礎 - 久保清隆のブログ @ライブレボリューション
※新しいブックマークボタンを使ってみた。簡単にコードを生成してくれるから便利。
詳細ははてなブックマークボタンの作成・設置について - はてなブックマーク
参考
- 作者: ジェレミ・D.ザウドニ,デレク・J.ベリング,Jeremy D. Zawodny,Derek J. Balling,林秀幸
- 出版社/メーカー: オライリージャパン
- 発売日: 2004/10
- メディア: 単行本
- 購入: 9人 クリック: 239回
- この商品を含むブログ (53件) を見る
◆◆このブログのサイトマップへ◆◆