SlideShare a Scribd company logo
⽚⼿間MySQLチューニング戦略
忙しいPHPer(とか)のための「最低限ここから、次のステップはこの
へん」
2017/10/08
⽇本MySQLユーザ会 yoku0825
phpcon 2017
TL;DR
スローログを出しましょう
InnoDBバッファプール (innodb_buffer_pool_size) は⼗分
⼤きくしましょう
インデックスを使いましょう
劇薬に⼿を出すのはやめましょう
1/77
まずはそ
こから
2/77
\こんにちは/
yoku0825@とある企業のDBA
オラクれない-
ポスグれない-
マイエスキューエる-
⽣息域
Twitter: @yoku0825-
Blog: ⽇々の覚��-
MyNA ML: ⽇本MySQLユーザ会-
MySQL Casual: Slack-
3/77
おしながき
スローログを出す
バッファプールの気持ちになる
インデックスの基本戦略を理解する
劇薬に⼿を出さない
4/77
おしながき
スローログを出す
バッファプールの気持ちになる
インデックスの基本戦略を理解する
劇薬に⼿を出さない
5/77
スローログを出す
スロークエリー(実⾏に⼀定時間以上時間がかかったクエリ
ー)を出⼒させるログ
これがないと「どのクエリーが遅かったのか」がそもそもわ
からない
performance_schema でもいいけどなかなかノイズが多いのでちょっと
わかる⼈向け
-
6/77
スローログ関連パラメーター
name default recommend
slow̲query̲log 0(OFF) 1(ON)
long̲query̲time 10 0.2(?)
log̲output FILE FILE
7/77
スローログを出す
slow̲query̲log
これをONにしないと始まらない
long̲query̲time
応答時間目標と合わせて。MySQLで200msかかるってことは全体ではもっ
とかかるはず
log̲output
FILE の場合テキストファイルに、 TABLE の場合 mysql.slow_log テーブル
(CSVストレージエンジン)に出⼒。 FILE,TABLE で両出⼒も可能。テーブ
ルに吐かせると並列性能ガタ落ち
8/77
ジェネラルログとは違うの︖
ジェネラルログ ( general_log )はクエリーをパースする
時点(=クエリー実⾏前)で吐き出すログ
実⾏に関する情報(何秒かかった、何⾏フェッチした、またはエラー
になったかなど)は何も持っていない
-
あとジェネラルログはロックがはるかにでかい
9/77
スローログの中味
# Time: 2017-10-02T12:51:35.321319+09:00
# User@Host: root[root] @ localhost [] Id: 78
# Query_time: 0.000497 Lock_time: 0.000176 Rows_sent: 8 Rows_ex
amined: 247
SET timestamp=1506916295;
SELECT code FROM country WHERE continent = 'Asia' AND region = 'E
astern Asia' ORDER BY population;
10/77
スローログの中味
Time
そのクエリーが「終了した」時刻。直前のスロークエリーと同じTimeの場
合、この⾏は省略される(5.6とそれ以前でよく⾒る風景)
User@Host
そのクエリーを実⾏したユーザーと接続元ホスト
Id
クエリーを実⾏したスレッドのコネクションID(SHOW PROCESSLIST で⾒え
るやつ)
11/77
スローログの中味
Query̲time
クエリーの実⾏にかかった時間
Lock̲time
ロックを取るまでに要した時間
Rows̲sent
そのクエリーが返送した結果セットの⾏数(更新系だと0になる)
Rows̲examined
そのクエリーが結果セットを作成するためにスキャンした⾏数
12/77
スローログの中味
# Time: 2017-10-02T12:51:35.321319+09:00
# User@Host: root[root] @ localhost [] Id: 78
# Query_time: 0.000497 Lock_time: 0.000176 Rows_sent: 8 Rows_ex
amined: 247
SET timestamp=1506916295;
SELECT code FROM country WHERE continent = 'Asia' AND region = 'E
astern Asia' ORDER BY population;
13/77
ポイント
Time が短期間に集中している︖
慢性的に遅いのか、何らかの要因があったのか
慢性的に遅い⽅がチューニングは楽なことが多い
再現しにくいスロークエリーのチューニングは⼿間がかかる
-
anemometerまたはそのラッパーのanemoeaterが便利-
14/77
時間分布を把握するためのツール
yoku0825/anemoeater
15/77
ポイント
Rows_examined / Rows_sent が⼗分⼩さいか︖
GROUP BY を使⽤している場合を除いた SELECT ステートメントでは1
が最も良い
-
返送するのに必要な⾏だけを綺麗にストレージから取り出していれば
1になる
-
これが⼤きいクエリーはインデックスでチューニングするのが楽-
あとはそもそも Rows_sent が本当にアプリケーションで必要
とされているのか︖
3万⾏くらい受け取ってるけど実際には100⾏くらいしか使ってなくて
アプリ側で捨てられてたこととか(つらい)
-
16/77
Rows_examined / Rows_sent 31くらい
# Time: 2017-10-02T12:51:35.321319+09:00
# User@Host: root[root] @ localhost [] Id: 78
# Query_time: 0.000497 Lock_time: 0.000176 Rows_sent: 8 Rows_ex
amined: 247
SET timestamp=1506916295;
SELECT code FROM country WHERE continent = 'Asia' AND region = 'E
astern Asia' ORDER BY population;
17/77
ポイント
WHERE 句の値が特定のものに偏っていないか︖
WHERE user_id = 1000 の時だけ遅いとか-
全ての値が均等に分布しているわけではない-
特定の値に対して使うインデックスを使い分けるなどちょっとコツが
いる
-
18/77
おしながき
スローログを出す
バッファプールの気持ちになる
インデックスの基本戦略を理解する
劇薬に⼿を出さない
19/77
おしながき
スローログを出す
バッファプールの気持ちになる
インデックスの基本戦略を理解する
劇薬に⼿を出さない
20/77
バッファプールの使われ⽅を知る
「物理メモリーの75%」とかよく語られる
InnoDBバッファプールはInnoDBの動作の核
なぜ⼤きくなければいけないのか
ただのキャッシュではない動き
21/77
⽤語
⽤語 意味 ファイル名 主なパラメーター
バッファプール メモリー上に確保さ
れるヒープ領域
N/A(オンメモリー) innodb_buffer_pool
_size
ログファイル 更新履歴を記録する
ファイル (*)
ib_logfile innodb_log_file_si
ze,
innodb_log_files_i
n_group
テーブルスペースフ
ァイル
バッファプールの中
⾝をストレージに写
し取ったもの
ibdata1, *.ibd innodb_file_per_ta
ble,
innodb_data_home_d
ir
(*) バイナリーログファイルとは別物
22/77
バッファプールに載っている状態でのSELECT
A
B
AA
C
B
E
D
F
A AA D C
SELECT .. FROM .. WHERE
InnoDB Buffer Pool
tablespace file
log file
23/77
バッファプールに載っていない状態でのSELECT
A
B
AA
C
B
E
D
F
A AA D C
SELECT .. FROM .. WHERE
InnoDB Buffer Pool
tablespace file
log file
24/77
バッファプールに載っていない状態でのSELECT
A
B
AA
C
B
E
D
F
B AA D C
SELECT .. FROM .. WHERE
InnoDB Buffer Pool
tablespace file
log file
25/77
バッファプールに載っていない状態でのSELECT
A
B
AA
C
B
E
D
F
B AA D C
SELECT .. FROM .. WHERE
InnoDB Buffer Pool
tablespace file
log file
26/77
バッファプールの使われ⽅その1
データやインデックスをキャッシュする
単位はページ(デフォルト16kB)
バッファプールにヒットした場合、ストレージアクセスはな
い
ミスヒットした場合のストレージアクセスはバックグラウンドスレッ
ド
-
27/77
バッファプールに載っている状態でのUPDATE
A
B
AA
C
B
E
D
F
A AA D C
UPDATE .. SET = 'X'
InnoDB Buffer Pool
tablespace file
log file
28/77
バッファプールに載っている状態でのUPDATE
A
B
AA
C
B
E
D
F
X AA D C
UPDATE .. SET = 'X'
InnoDB Buffer Pool
tablespace file
log file
A => X
29/77
この状態でSELECTが⾛ると
A
B
AA
C
B
E
D
F
X AA D C
SELECT .. FROM .. WHERE
InnoDB Buffer Pool
tablespace file
log file
A => X
30/77
テーブルスペースファイルへの反映は非同期
X
B
AA
C
B
E
D
F
X AA D C
InnoDB Buffer Pool
tablespace file
log file
A => X
31/77
バッファプールの使われ⽅その2
コミット時に同期的に更新されるのはバッファプールとログ
ファイルのみ
それでも読み出すデータは(当然)影響を受けない-
バッファプール(メモリー)とログファイル(シーケンシャルライ
ト)だけの書き込みで⾼速化
-
テーブルスペースファイルへの反映は非同期
テーブルスペースファイルへの書き込みはランダムライトになるので
やや遅い
-
コミット後、テーブルスペースファイルに未反映の��ージをダーテ
ィーページと呼ぶ
-
32/77
ダーティーページがある状態でSELECT⽤のページが⾜り
なくなると
A
B
AA
C
B
E
D
F
X AA D C
SELECT .. FROM .. WHERE
InnoDB Buffer Pool
tablespace file
log file
A => X
33/77
ダーティーページがある状態でSELECT⽤のページが⾜り
なくなると
A
B
AA
C
B
E
D
F
X AA D C
SELECT .. FROM .. WHERE
InnoDB Buffer Pool
tablespace file
log file
A => X
34/77
その時点でそのページに関するログをテーブルスペースフ
ァイルに反映して
X
B
AA
C
B
E
D
F
X AA D C
SELECT .. FROM .. WHERE
InnoDB Buffer Pool
tablespace file
log file
A => X
35/77
空いたページにSELECTしたいページを載せてから
X
B
AA
C
B
E
D
F
B AA D C
SELECT .. FROM .. WHERE
InnoDB Buffer Pool
tablespace file
log file
A => X
36/77
データを返す
X
B
AA
C
B
E
D
F
B AA D C
SELECT .. FROM .. WHERE
InnoDB Buffer Pool
tablespace file
log file
A => X
37/77
バッファプールの使われ⽅その3
SELECTしただけなのに 書き込みが⾛った
バッファプールに余裕があって
空きページがあれば-
追い出すページがダーティーページでなければ
実際はもうちょっとインテリジェントに判定する
-
こんなことにはならない-
5.6とそれ以降はWrite on SELECTを避けるための
Adaptive Flushingという仕組みがある
38/77
INSERTもほぼ同様
INSERT INTO VALUES ('N')
A
B
AA
C
B
E
D
F
AA D C
InnoDB Buffer Pool
tablespace file
log file
A
39/77
INSERTもほぼ同様
INSERT INTO VALUES ('N')
A
B
AA
C
B
E
D
F
AA D C
InnoDB Buffer Pool
tablespace file
log file
40/77
INSERTもほぼ同様
A
B
AA
C
B
E
D
F
AA D C
INSERT INTO VALUES ('N')
InnoDB Buffer Pool
tablespace file
log file
N
41/77
INSERTもほぼ同様
A
B
AA
C
B
E
D
F
AA D C
INSERT INTO VALUES ('N')
InnoDB Buffer Pool
tablespace file
log file
N
N/A => N
42/77
INSERTもほぼ同様
A
B
AA
C
B
E
D
F
AA D C
InnoDB Buffer Pool
tablespace file
log file
N
N/A => N
43/77
INSERTもほぼ同様
A
B
AA
C
B
E
D
F
AA D C
InnoDB Buffer Pool
tablespace file
log file
N
N/A => N
N
44/77
DELETEだってバッファプールを使う
DELETE FROM .. WHERE
A
B
AA
C
B
E
D
F
AA D C
InnoDB Buffer Pool
tablespace file
log file
A
45/77
DELETEだってバッファプールを使う
DELETE FROM .. WHERE
A
B
AA
C
B
E
D
F
AA D C
InnoDB Buffer Pool
tablespace file
log file
46/77
DELETEだってバッファプールを使う
DELETE FROM .. WHERE
A
B
AA
C
B
E
D
F
AA D C
InnoDB Buffer Pool
tablespace file
log file
B
47/77
DELETEだってバッファプールを使う
DELETE FROM .. WHERE
A
B
AA
C
B
E
D
F
AA D C
InnoDB Buffer Pool
tablespace file
log file
48/77
DELETEだってバッファプールを使う
DELETE FROM .. WHERE
A
B
AA
C
B
E
D
F
AA D C
InnoDB Buffer Pool
tablespace file
log file
B => N/A
49/77
DELETEだってバッファプールを使う
A
B
AA
C
B
E
D
F
AA D C
InnoDB Buffer Pool
tablespace file
log file
B => N/A
50/77
まとめ
実はCRUD全ての動作にバッファプールが使われている
ただのキャッシュだと思って当たると思わぬWrite on SELECTに遭遇
する
-
余裕を持ったサイジングと「無駄遣��しない努⼒」
InnoDB圧縮 (ROW_FORMAT=COMPRESSED)は圧縮前と圧縮後の 両⽅ がバ
ッファプールに載る
ストレージを稼ぐためにメモリーを犠牲にするような戦略
-
次に続く話-
51/77
おしながき
スローログを出す
バッファプールの気持ちになる
インデックスの基本戦略を理解する
劇薬に⼿を出さない
52/77
おしながき
スローログを出す
バッファプールの気持ちになる
インデックスの基本戦略を理解する
劇薬に⼿を出さない
53/77
インデックスの概観図
セカンダリーインデックスは「ソート済みのデータの部分複製」
root club
spade
2
3
2
13
20
18
45
77
54/77
インデックスの概観図
InnoDBはこのツリーを「右または下に だけ 移動できる」
root club
spade
2
3
2
13
20
18
45
77
55/77
インデックスの概観図
セカンダリーインデックスのリーフノードには「PRIMARY KEY
の値」
root club
spade
2
3
2
13
20
18
45
77
56/77
InnoDBクラスターインデックスのイメージ
root club
spade
2
3
A
13
20
18
45
2
23
root p2
p13
p18
p20
p23
p45
Spade-A
Club-2
1
Club-3
Club-2
Spade-A
Club-3
Secondary Index Clustered Index
57/77
MySQLの(インデックスの)得意な操作
特定のキーの値を狙い撃ち( = 演算⼦と AND 演算⼦)
概観図でいうところの「右に進む」操作-
IN 演算⼦なんかも効くっちゃ効くけど、 ORDER BY まで波及しないケ
ースあり
-
リーフノードが並んでいる順番での ORDER BY
概観図でいうところの「下に進む」操作-
EXPLAIN で Extra: Using filesort になっているケースの⾼速化-
JOIN はこれを狙うのに慣れがいるので敬遠されがち-
58/77
簡単な憶え⽅
インデックスは WHERE 句のカラムを列挙してから ORDER BY
句のカラムを列挙する
= と AND しか使ってない場合はこれでいける
SELECT ..
FROM country
WHERE continent = 'Asia' AND
region = 'Eastern Asia'
ORDER BY population;
↓
KEY(continent, region, population)
59/77
簡単な憶え⽅(︖)
こう︖
SELECT ..
FROM country JOIN
countrylanguage ON country.code= countrylanguage.countrycode
WHERE country.continent = 'Asia'
ORDER BY countrylanguage.percentage LIMIT 5;
↓
country: KEY(continent)
countrylanguage: KEY(countrycode, percentage)
60/77
簡単な憶え⽅(︖)
*************************** 1. row ***************************
id: 1
select_type: SIMPLE
table: country
partitions: NULL
type: ref
possible_keys: PRIMARY,idx_continent
key: idx_continent
key_len: 1
ref: const
rows: 51
filtered: 100.00
Extra: Using temporary; Using filesort
*************************** 2. row ***************************
id: 1
select_type: SIMPLE
table: countrylanguage
partitions: NULL
type: ref
possible_keys: PRIMARY,CountryCode,idx_countrycode_percentage
key: PRIMARY
key_len: 3
ref: world.country.Code
rows: 4
filtered: 100.00
Extra: NULL
61/77
簡単じゃない憶え⽅
⼀番速くなるのは実はこう
SELECT ..
FROM country JOIN
countrylanguage ON country.code= countrylanguage.countrycode
WHERE country.continent = 'Asia'
ORDER BY countrylanguage.percentage LIMIT 5;
↓
country: KEY(code, continent)
countrylanguage: KEY(percentage)
62/77
簡単じゃない憶え⽅
*************************** 1. row ***************************
id: 1
select_type: SIMPLE
table: countrylanguage
partitions: NULL
type: index
possible_keys: NULL
key: idx_percentage
key_len: 4
ref: NULL
rows: 5
filtered: 100.00
Extra: NULL
*************************** 2. row ***************************
id: 1
select_type: SIMPLE
table: country
partitions: NULL
type: ref
possible_keys: idx_code_continent
key: idx_code_continent
key_len: 4
ref: world.countrylanguage.CountryCode,const
rows: 1
filtered: 100.00
Extra: NULL
63/77
JOINだと簡単な憶え⽅が通⽤しにくい(´・ω・`)
とはいえ平均的に⾏数が絞り込めるので、体感で遅くなるま
ではこれで⼗分戦える
詳しく知りたい⽅は Where狙いのキー、order by狙いのキ
ー のスライドをどうぞ
64/77
ポイント
AND と = 以外の演算⼦を ORDER BY と混ぜて使わない
概観図でいうところの「左に⾏く」必要がありそうな動作-
NOT, IN, <, >, OR などなど-
使っている場合、AND と = だけの形に落とし込めないか︖
5.7とそれ以降はgenerated columnで式インデックスが使える
-
インデックスを張ってあるカラムに対する演算をしない
⾏から値を取り出して計算するまでWHERE句の評価ができない = 不
要な⾏までフェッチして評価してしまう
-
NG: WHERE price * 1.08 = 108-
OK: WHERE price = 100-
65/77
インデックスを綺麗に使うと
バッファプールを⼤事に使える
テーブルデータよりもインデックスの⽅が⼩さい
ミスヒット率が下がる
-
そもそもバッファプールに出⼊りするページの数が減る
非同期のダーティーページフラッシュで⼗分戦える
-
デフォルトのInnoDBのロックはネクストキーロック
インデックスで絞り込めれば絞り込めるほど、ロックの粒度が⼩さく
なっていく
-
66/77
おしながき
スローログを出す
バッファプールの気持ちになる
インデックスの基本戦略を理解する
劇薬に⼿を出さない
67/77
おしながき
スローログを出す
バッファプールの気持ちになる
インデックスの基本戦略を理解する
劇薬に⼿を出さない
68/77
MySQLの劇薬
skip_innodb_doublewrite
innodb_flush_log_at_trx_commit <> 1
sync_binlog <> 1
パラメーター名に unsafe とか⼊ってるやつ
MyISAMストレージエンジン
69/77
MySQLの劇薬
今までやってきたのが何だったんだってくらい性能が上がる
(こともある)
ただし、これらの設定はだいたい「クラッシュ時のデータの
保全性」を犠牲にしている
mysqld が落ちた後、データが壊れているかも知れない-
壊れているならまだしも、黙って抜け落ちているかも知れない-
70/77
ダメ、絶
対
71/77
まとめ
スローログを出しましょう
InnoDBバッファプール (innodb_buffer_pool_size) は⼗分
⼤きくしましょう
インデックスを使いましょう
劇薬に⼿を出すのはやめましょう
72/77
おまけ
バージョンを上げるにつれ、オンラインで変更できるパラメ
ーターが増えている
バージョンはなるべく新しいものにした⽅がいくらでも取り返しがつ
くように
-
MySQL 5.7だとついにオンラインでバッファプールのサイズが変更で
きるようになったし
-
73/77
And next…
ダーティーページが溜まり続けるとログファイルを書ききっ
てしまうかも
SHOW PROCESSLIST, SHOW GLOBAL STATUS などで現状を可視化
していく
SHOW ENGINE INNODB STATUS からトランザクションの様⼦に
目を配る
74/77
And next…
information_schema.innodb_lock_waits から競合している
ロックを探して更にインデックスを⾜す
EXPLAIN と仲良くなって JOIN でもORDER BY狙いのキーを
狙っていく
information_schema.innodb_buffer_page, ib_buffer_pool
からどのテーブルがどの程度バッファプールを占めているの
かを確認する
memcachedにキャッシュさせる(ぁ
75/77
Further more…
⼀通りこの辺を調べてなお深く知りたくなったら、MySQL
の英霊を召喚するといいと思うの
⽇本MySQLユーザ会
MySQL Mailing List-
MySQL Casual
MySQL CasualのSlackへの参加-
76/77
Questions
and/or
Suggestions?
77/77

More Related Content

片手間MySQLチューニング戦略