Upload
yoku0825
View
13.350
Download
1
Embed Size (px)
DESCRIPTION
2014/08/29 YAPC::Asia 2014
Citation preview
WHERE狙いのキー、ORDER BY狙いのキー
2014/08/28yoku0825
YAPC::Asia Tokyo 2014
みんなEXPLAINしてるー?
目XPLAINでも可
type: ALL
邪悪ですね
Extra: Using where; Using filesort
うーん…
でもなんで?
クエリーが遅い時にこいつらが出るのか、こいつらが出る時にクエリーが遅くなるのか
という話をコードで例えて
お話ししたいと思いますPerlがへたっぴなのは大目にみてくだしあ。。
今日の意気込み
Kuso-QueryAsACode
I'm yoku0825
● とある企業のDBA● オラクれない● ポスグれない● マイエスキューエる
● 家に帰ると● 嫁の夫● せがれの父
● 馬鹿だからかわいいわけじゃなくて、かわいいイルカがたまたまバカだった
はじめに
● サンプルデータは MySQLのサンプルデータベース(worldデータベース)からインデックスを全て取っ払ったものです● http://dev.mysql.com/doc/index-other.html
● コードはgithubに上げてあります● https://github.com/yoku0825/yapc_2014● すごく…ウンコードです…
はじめに
● 原則、MySQLは 1つのテーブルにつき同時に1つのインデックスしか使いません● Index mergeとかあるけどアレは例外だし狙ってやっても速くなる訳でもないので除外
● 自己結合ジョインは別
● この資料はBKAや ICP, MRRとかには対応していません● リードバッファやジョインバッファも非対応● 雰囲気だけなんとなく感じてください
As A Codeとか言いながら最初にトランプの話をします。
100枚のトランプの山があります。
何組かのトランプを混ぜたもので何が何枚入ってるかはわかりません。
この山の中からスペードのAを探してください
これがテーブルスキャン
SELECT * FROM cardsWHERE mark= 'spade' AND number= 'A';
面倒なので、チートシートを作ります
マーク 上から
club 4,13,25,28,31,34,35,36,37,39,43,46,47,54,62,66,71,74,77,84,85,86,89,96,100
diamond 1,5,15,23,24,26,32,33,41,45,48,49,51,57,63,65,67,68,73,76,80,81,82,90,93,95,98
heart 2,3,7,11,12,17,22,27,29,50,52,53,56,58,64,69,70,72,78,87,92,94
spade 6,8,9,10,14,16,18,19,20,21,30,38,40,42,44,55,59,60,61,75,79,83,88,91,97,99
これがWHERE句を部分的にインデックスで解決したパターン
SELECT * FROM cardsWHERE mark= 'spade' AND number= 'A';
マーク 数字 上から
club A 54,77
club 3 74,84
..
heart K 17,22,69
spade 2 16,20
spade 3 21
spade 4 40
spade 5 88,99
..
これがWHERE句を全てインデックスで解決したパターン
SELECT * FROM cardsWHERE mark= 'spade' AND number= 'A';
このチートシートを使っていいから、山の中からハートを
KからAに向かって並べておくれSELECT * FROM cardsWHERE mark= 'heart'ORDER BY number DESC;
マーク 数字 上から
club 1 54,77
club 3 74,84
..
heart A 29,70
..
heart J 53,58
heart Q 50
heart K 17,22,69
..
マーク 数字 上から
club 1 54,77
club 3 74,84
..
heart A 29,70
..
heart J 53,58
heart Q 50
heart K 17,22,69
..
インデックスとはソート済みのデータの複製
異論はあると思う
じゃあコードに行きます
テーブルデータ
$VAR1 = [ { 'population' => '103000', 'region' => 'Caribbean', 'name' => 'Aruba', 'continent' => 'North America', 'code' => 'ABW', .. }, { 'population' => '22720000', 'region' => 'Southern and Central Asia', 'name' => 'Afghanistan', 'continent' => 'Asia', 'code' => 'AFG', .. }, { 'population' => '12878000', 'region' => 'Central Africa', 'name' => 'Angola', 'continent' => 'Africa', 'code' => 'AGO', .. }, ..
display_table_structure.pl
hashrefを要素にしたarrayrefで表現
テーブルデータ
$VAR1 = [ { 'population' => '103000', 'region' => 'Caribbean', 'name' => 'Aruba', 'continent' => 'North America', 'code' => 'ABW', .. }, { 'population' => '22720000', 'region' => 'Southern and Central Asia', 'name' => 'Afghanistan', 'continent' => 'Asia', 'code' => 'AFG', .. }, { 'population' => '12878000', 'region' => 'Central Africa', 'name' => 'Angola', 'continent' => 'Africa', 'code' => 'AGO', .. }, ..
my $row= $table->[0];
my $row= $table->[1];print $row->{name}; ## Afghanistan
display_table_structure.pl
テーブルスキャン
mysql56> EXPLAIN SELECT Name, Continent, Population -> FROM Country -> WHERE Continent = 'Asia' -> ORDER BY Population LIMIT 5;+----+-------------+---------+------+---------------+------+---------+------+------+-----------------------------+| id | select_type | table | type | possible_keys | key | key_len | ref | rows | Extra |+----+-------------+---------+------+---------------+------+---------+------+------+-----------------------------+| 1 | SIMPLE | Country | ALL | NULL | NULL | NULL | NULL | 239 | Using where; Using filesort |+----+-------------+---------+------+---------------+------+---------+------+------+-----------------------------+1 row in set (0.00 sec)
* 1行ずつデータをフェッチして* WHERE句のカラムで評価し* ソートバッファに詰め込み* クイックソートして* 先頭から5件取り出す
テーブルスキャン
for (my $rownum= 0; $rownum < scalar(@$country_table); $rownum++){ my $row= $country_table->[$rownum];
$evaluted++; if ($row->{continent} eq "Asia") { $sorted++; push(@{$sort_buffer->{$row->{population}}}, $rownum); }}
my $sorted_buffer= filesort_single_column($sort_buffer);
foreach my $rownum (@$sorted_buffer){ my $row= $country_table->[$rownum];
printf("%s\t%s\t%d\n", $row->{name}, $row->{continent}, $row->{population}); if (++$count >= 5) {last;}}
scan_where_scan_orderby.pl
テーブルスキャン
for (my $rownum= 0; $rownum < scalar(@$country_table); $rownum++){ my $row= $country_table->[$rownum];
$evaluted++; if ($row->{continent} eq "Asia") { $sorted++; push(@{$sort_buffer->{$row->{population}}}, $rownum); }}
my $sorted_buffer= filesort_single_column($sort_buffer);
foreach my $rownum (@$sorted_buffer){ my $row= $country_table->[$rownum];
printf("%s\t%s\t%d\n", $row->{name}, $row->{continent}, $row->{population}); if (++$count >= 5) {last;}}
1行ずつフェッチして
scan_where_scan_orderby.pl
テーブルスキャン
for (my $rownum= 0; $rownum < scalar(@$country_table); $rownum++){ my $row= $country_table->[$rownum];
$evaluted++; if ($row->{continent} eq "Asia") { $sorted++; push(@{$sort_buffer->{$row->{population}}}, $rownum); }}
my $sorted_buffer= filesort_single_column($sort_buffer);
foreach my $rownum (@$sorted_buffer){ my $row= $country_table->[$rownum];
printf("%s\t%s\t%d\n", $row->{name}, $row->{continent}, $row->{population}); if (++$count >= 5) {last;}}
WHERE句で評価
scan_where_scan_orderby.pl
テーブルスキャン
for (my $rownum= 0; $rownum < scalar(@$country_table); $rownum++){ my $row= $country_table->[$rownum];
$evaluted++; if ($row->{continent} eq "Asia") { $sorted++; push(@{$sort_buffer->{$row->{population}}}, $rownum); }}
my $sorted_buffer= filesort_single_column($sort_buffer);
foreach my $rownum (@$sorted_buffer){ my $row= $country_table->[$rownum];
printf("%s\t%s\t%d\n", $row->{name}, $row->{continent}, $row->{population}); if (++$count >= 5) {last;}}
マッチしたらソートバッファに詰める
scan_where_scan_orderby.pl
テーブルスキャン
for (my $rownum= 0; $rownum < scalar(@$country_table); $rownum++){ my $row= $country_table->[$rownum];
$evaluted++; if ($row->{continent} eq "Asia") { $sorted++; push(@{$sort_buffer->{$row->{population}}}, $rownum); }}
my $sorted_buffer= filesort_single_column($sort_buffer);
foreach my $rownum (@$sorted_buffer){ my $row= $country_table->[$rownum];
printf("%s\t%s\t%d\n", $row->{name}, $row->{continent}, $row->{population}); if (++$count >= 5) {last;}}
ソートバッファの中身をクイックソート
scan_where_scan_orderby.pl
テーブルスキャン
for (my $rownum= 0; $rownum < scalar(@$country_table); $rownum++){ my $row= $country_table->[$rownum];
$evaluted++; if ($row->{continent} eq "Asia") { $sorted++; push(@{$sort_buffer->{$row->{population}}}, $rownum); }}
my $sorted_buffer= filesort_single_column($sort_buffer);
foreach my $rownum (@$sorted_buffer){ my $row= $country_table->[$rownum];
printf("%s\t%s\t%d\n", $row->{name}, $row->{continent}, $row->{population}); if (++$count >= 5) {last;}}
先頭の5件を出力したらそこでループを抜ける
scan_where_scan_orderby.pl
テーブルスキャン
$ ./scan_where_scan_orderby.plMaldives Asia 286000Brunei Asia 328000Macao Asia 473000Qatar Asia 599000Bahrain Asia 617000Total rows evaluted are 239, sorted are 51.
scan_where_scan_orderby.pl
5行の出力に対して、239回のWHERE評価と51行のファイルソート
そりゃあ遅いよテーブルスキャン
インデックス作りましょう
インデックス
$VAR1 = [ { 'Africa' => [ 2, 17, 19, .., ] }, { 'Antarctica' => [ 11, 12, 34, .., ] }, { 'Asia' => [ 1, 7, 9, ..,
カラムの値をkey,対応する行のプライマリーキーの
arrayrefを valueにしたhashrefの arrayref
(ソート済みを表現したかった)
display_single_column_index_structure.pl
インデックス
$VAR1 = [ { 'Africa' => [ 2, 17, 19, .., ] }, { 'Antarctica' => [ 11, 12, 34, .., ] }, { 'Asia' => [ 1, 7, 9, ..,
display_single_column_index_structure.pl
Continent = 'Africa'な行IDを取り出すには
$index->[0]->{Africa}(arrayrefとして取り出す)
インデックス
$VAR1 = [ { 'Africa' => [ 2, 17, 19, .., ] }, { 'Antarctica' => [ 11, 12, 34, .., ] }, { 'Asia' => [ 1, 7, 9, ..,
$VAR1 = [ { 'population' => '103000', 'region' => 'Caribbean', 'name' => 'Aruba', 'continent' => 'North America', 'code' => 'ABW', .. }, { 'population' => '22720000', 'region' => 'Southern and Central Asia', 'name' => 'Afghanistan', 'continent' => 'Asia', 'code' => 'AFG', .. }, { 'population' => '12878000', 'region' => 'Central Africa', 'name' => 'Angola', 'continent' => 'Africa', 'code' => 'AGO', .. }, ..
$table->[$index->[2]->{Asia}->[0]]
ちょっとつらい
$index->[$n]->{hoge}ここがマジックナンバー
インデックス
$VAR1 = { 'map' => { 'Oceania' => 5, 'North America' => 4, 'Europe' => 3, 'South America' => 6, 'Asia' => 2, 'Africa' => 0, 'Antarctica' => 1 }, 'index' => [ [ 2, 17, 19, .. ], [ 11, 12, 34, .. ], ..
ソート済みの順番を$index->{map}に押し込めた
display_single_column_index_structure.pl
こっちはarrayrefの arrayrefに
$VAR1 = [ { 'population' => '103000', 'region' => 'Caribbean', 'name' => 'Aruba', 'continent' => 'North America', 'code' => 'ABW', .. }, { 'population' => '22720000', 'region' => 'Southern and Central Asia', 'name' => 'Afghanistan', 'continent' => 'Asia', 'code' => 'AFG', .. }, { 'population' => '12878000', 'region' => 'Central Africa', 'name' => 'Angola', 'continent' => 'Africa', 'code' => 'AGO', .. }, ..
インデックス
$VAR1 = { 'map' => { 'Oceania' => 5, 'North America' => 4, 'Europe' => 3, 'South America' => 6, 'Asia' => 2, 'Africa' => 0, 'Antarctica' => 1 }, 'index' => [ [ 2, 17, 19, .. ], [ 11, 12, 34, .. ], ..
display_single_column_index_structure.pl
$index_num= $index->{map}->{Africa}
$rownum_array= $index->{index}->[$index_num}
foreach my $rownum(@$rownum_array){ my $row= $table->[$rownum]; ..}
うわあ。。
ごめんなさい。。
WHERE狙いのキー
mysql56> ALTER TABLE Country ADD KEY index_continent(continent);Query OK, 0 rows affected (0.07 sec)Records: 0 Duplicates: 0 Warnings: 0
mysql56> EXPLAIN SELECT Name, Continent, Population -> FROM Country -> WHERE Continent = 'Asia' -> ORDER BY Population LIMIT 5;+----+-------------+---------+------+-----------------+-----------------+---------+-------+------+----------------------------------------------------+| id | select_type | table | type | possible_keys | key | key_len | ref | rows | Extra |+----+-------------+---------+------+-----------------+-----------------+---------+-------+------+----------------------------------------------------+| 1 | SIMPLE | Country | ref | index_continent | index_continent | 1 | const | 51 | Using index condition; Using where; Using filesort |+----+-------------+---------+------+-----------------+-----------------+---------+-------+------+----------------------------------------------------+1 row in set (0.00 sec)
* WHERE句のレンジをインデックスから取り出し* ソートバッファに詰め込み* クイックソートして* 先頭から5件取り出す
WHERE狙いのキー
my $index_num = $country_index->{map}->{Asia};my $rownum_array= $country_index->{index}->[$index_num];
foreach my $rownum (@$rownum_array){ my $row= $country_table->[$rownum];
$sorted++; push(@{$sort_buffer->{$row->{population}}}, $rownum);}
my $sorted_buffer= filesort_single_column($sort_buffer);
foreach my $rownum (@$sorted_buffer){ my $row= $country_table->[$rownum];
printf("%s\t%s\t%d\n", $row->{name}, $row->{continent}, $row->{population}); if (++$count >= 5) {last;}}
indexed_where_scan_orderby.pl
WHERE狙いのキー
my $index_num = $country_index->{map}->{Asia};my $rownum_array= $country_index->{index}->[$index_num];
foreach my $rownum (@$rownum_array){ my $row= $country_table->[$rownum];
$sorted++; push(@{$sort_buffer->{$row->{population}}}, $rownum);}
my $sorted_buffer= filesort_single_column($sort_buffer);
foreach my $rownum (@$sorted_buffer){ my $row= $country_table->[$rownum];
printf("%s\t%s\t%d\n", $row->{name}, $row->{continent}, $row->{population}); if (++$count >= 5) {last;}}
WHERE句のレンジをインデックスから取り出す
indexed_where_scan_orderby.pl
WHERE狙いのキー
my $index_num = $country_index->{map}->{Asia};my $rownum_array= $country_index->{index}->[$index_num];
foreach my $rownum (@$rownum_array){ my $row= $country_table->[$rownum];
$sorted++; push(@{$sort_buffer->{$row->{population}}}, $rownum);}
my $sorted_buffer= filesort_single_column($sort_buffer);
foreach my $rownum (@$sorted_buffer){ my $row= $country_table->[$rownum];
printf("%s\t%s\t%d\n", $row->{name}, $row->{continent}, $row->{population}); if (++$count >= 5) {last;}}
取り出した行を全部ソートバッファに詰めて
indexed_where_scan_orderby.pl
WHERE狙いのキー
my $index_num = $country_index->{map}->{Asia};my $rownum_array= $country_index->{index}->[$index_num];
foreach my $rownum (@$rownum_array){ my $row= $country_table->[$rownum];
$sorted++; push(@{$sort_buffer->{$row->{population}}}, $rownum);}
my $sorted_buffer= filesort_single_column($sort_buffer);
foreach my $rownum (@$sorted_buffer){ my $row= $country_table->[$rownum];
printf("%s\t%s\t%d\n", $row->{name}, $row->{continent}, $row->{population}); if (++$count >= 5) {last;}}
indexed_where_scan_orderby.pl
クイックソートして
WHERE狙いのキー
my $index_num = $country_index->{map}->{Asia};my $rownum_array= $country_index->{index}->[$index_num];
foreach my $rownum (@$rownum_array){ my $row= $country_table->[$rownum];
$sorted++; push(@{$sort_buffer->{$row->{population}}}, $rownum);}
my $sorted_buffer= filesort_single_column($sort_buffer);
foreach my $rownum (@$sorted_buffer){ my $row= $country_table->[$rownum];
printf("%s\t%s\t%d\n", $row->{name}, $row->{continent}, $row->{population}); if (++$count >= 5) {last;}}
indexed_where_scan_orderby.pl
先頭の5件を出力したらループを抜ける
WHERE狙いのキー
$ ./indexed_where_scan_orderby.plMaldives Asia 286000Brunei Asia 328000Macao Asia 473000Qatar Asia 599000Bahrain Asia 617000Total rows evaluted are 1, sorted are 51.
indexed_where_scan_orderby.pl
WHEREの評価は1回で済むけど51行をファイルソート
次はORDER BY狙い
インデックス
$VAR1 = [ { '0' => [ 11, 12, 34, 93, 100, 187, 221 ] }, { '50' => [ 166 ] }, { '600' => [ 38 ] }, ..
既にソート済みなので、先頭から順番に
取り出すだけで良い
[ $table->[11], $table->[12], $table->[34], $table->[93], $table->[100], $table->[187], $table->[221], $table->[166], $table->[38], ..]
ORDER BY狙いのキー
mysql56> ALTER TABLE Country ADD KEY index_population(population);Query OK, 0 rows affected (0.07 sec)Records: 0 Duplicates: 0 Warnings: 0
mysql56> EXPLAIN SELECT Name, Continent, Population -> FROM Country -> WHERE Continent = 'Asia' -> ORDER BY Population LIMIT 5;+----+-------------+---------+-------+---------------+------------------+---------+------+------+-------------+| id | select_type | table | type | possible_keys | key | key_len | ref | rows | Extra |+----+-------------+---------+-------+---------------+------------------+---------+------+------+-------------+| 1 | SIMPLE | Country | index | NULL | index_population | 4 | NULL | 5 | Using where |+----+-------------+---------+-------+---------------+------------------+---------+------+------+-------------+1 row in set (0.00 sec)
* インデックスに沿って行を取り出し* WHERE句にマッチするか判定して* 5件データが揃ったらループから抜ける
ORDER BY狙いのキー
my $cardinality= scalar(keys(%{$index->{map}}));LOOP: for (my $index_num= 0; $index_num < $cardinality; $index_num++){ my $rownum_array= $index->{index}->[$index_num];
foreach my $rownum (@$rownum_array) { my $row= $country_table->[$rownum];
$evaluted++; if ($row->{continent} eq "Asia") { printf("%s\t%s\t%d\n", $row->{name}, $row->{continent}, $row->{population}); if (++$count >= 5) {last LOOP;} } }}
scan_where_indexed_orderby.pl
ORDER BY狙いのキー
my $cardinality= scalar(keys(%{$index->{map}}));LOOP: for (my $index_num= 0; $index_num < $cardinality; $index_num++){ my $rownum_array= $index->{index}->[$index_num];
foreach my $rownum (@$rownum_array) { my $row= $country_table->[$rownum];
$evaluted++; if ($row->{continent} eq "Asia") { printf("%s\t%s\t%d\n", $row->{name}, $row->{continent}, $row->{population}); if (++$count >= 5) {last LOOP;} } }}
インデックスに沿って行を取り出して
scan_where_indexed_orderby.pl
ORDER BY狙いのキー
my $cardinality= scalar(keys(%{$index->{map}}));LOOP: for (my $index_num= 0; $index_num < $cardinality; $index_num++){ my $rownum_array= $index->{index}->[$index_num];
foreach my $rownum (@$rownum_array) { my $row= $country_table->[$rownum];
$evaluted++; if ($row->{continent} eq "Asia") { printf("%s\t%s\t%d\n", $row->{name}, $row->{continent}, $row->{population}); if (++$count >= 5) {last LOOP;} } }}
WHERE句にマッチするか判定して
scan_where_indexed_orderby.pl
ORDER BY狙いのキー
my $cardinality= scalar(keys(%{$index->{map}}));LOOP: for (my $index_num= 0; $index_num < $cardinality; $index_num++){ my $rownum_array= $index->{index}->[$index_num];
foreach my $rownum (@$rownum_array) { my $row= $country_table->[$rownum];
$evaluted++; if ($row->{continent} eq "Asia") { printf("%s\t%s\t%d\n", $row->{name}, $row->{continent}, $row->{population}); if (++$count >= 5) {last LOOP;} } }}
先頭の5件を出力したらそこでループを抜ける
scan_where_indexed_orderby.pl
ORDER BY狙いのキー
$ ./scan_where_indexed_orderby.plMaldives Asia 286000Brunei Asia 328000Macao Asia 473000Qatar Asia 599000Bahrain Asia 617000Total rows evaluted are 79, sorted are 0.
scan_where_indexed_orderby.pl
ソートのオーバーヘッドはないものの、79行をWHERE句評価
WHEREと ORDER BYを両方カバーするキー
複合インデックス
$VAR1 = [ { 'Africa' => [ { '0' => [ 100 ] }, { '6000' => [ 188 ] }, .. ] }, { 'Antarctica' => [ { '0' => [ 11, 12, 34, 93, 187 ] } ] }, .. display_double_column_index_structure.pl
複合インデックス
$VAR1 = [ { 'Africa' => [ { '0' => [ 100 ] }, { '6000' => [ 188 ] }, .. ] }, { 'Antarctica' => [ { '0' => [ 11, 12, 34, 93, 187 ] } ] }, ..
my $african= $index->[0]->{Africa};my $african_6000_people= $african->[1]->{6000};foreach my $rownum (@$african_6000_people){ my $row= $table->[$rownum]; ..}
display_double_column_index_structure.pl
複合インデックス
$VAR1 = { 'map' => { 'Oceania' => { '83000' => 12, '3862000' => 22, '235000' => 19, 'self' => 5, .. }, 'North America' => { '154000' => 14, '21000' => 4, .. }, } }, 'index' => [ [ [ 100 ], [ 188 ], ..
display_double_column_index_structure.pl
複合インデックス
$VAR1 = { 'map' => { 'Oceania' => { '83000' => 12, '3862000' => 22, '235000' => 19, 'self' => 5, .. }, 'North America' => { '154000' => 14, '21000' => 4, .. }, } }, 'index' => [ [ [ 100 ], [ 188 ], ..
display_double_column_index_structure.pl
マジックナンバーよけをしたらかなりカオス
selfを予約語にしてるのがものすごくイケてない
複合インデックス
$VAR1 = { 'map' => { 'Oceania' => { '83000' => 12, '3862000' => 22, '235000' => 19, 'self' => 5, .. }, 'North America' => { '154000' => 14, '21000' => 4, .. }, } }, 'index' => [ [ [ 100 ], [ 188 ], ..
display_double_column_index_structure.pl
my $index_num= $index->{map}->{Oceania}->{83000};
my $rownum_array= $index->{index}->[$index_num];
foreach ..
複合インデックス
$VAR1 = { 'map' => { 'Oceania' => { '83000' => 12, '3862000' => 22, '235000' => 19, 'self' => 5, .. }, 'North America' => { '154000' => 14, '21000' => 4, .. }, } }, 'index' => [ [ [ 100 ], [ 188 ], ..
display_double_column_index_structure.pl
my $index_nums_hash= $index->{map}->{Oceania}->{self};
foreach my $index_num (values(%$index_nums_hash))..
Foreach ..
\カオス!/
すいませんすいません。。
WHEREと ORDER BYを両方カバーするキー
mysql56> ALTER TABLE Country ADD KEY index_continent_population(continent, population);Query OK, 0 rows affected (0.07 sec)Records: 0 Duplicates: 0 Warnings: 0
mysql56> EXPLAIN SELECT Name, Continent, Population -> FROM Country -> WHERE Continent = 'Asia' -> ORDER BY Population LIMIT 5;+----+-------------+---------+------+----------------------------+----------------------------+---------+-------+------+-------------+| id | select_type | table | type | possible_keys | key | key_len | ref | rows | Extra |+----+-------------+---------+------+----------------------------+----------------------------+---------+-------+------+-------------+| 1 | SIMPLE | Country | ref | index_continent_population | index_continent_population | 33 | const | 51 | Using where |+----+-------------+---------+------+----------------------------+----------------------------+---------+-------+------+-------------+1 row in set (0.00 sec)
* WHERE句のレンジをインデックスから取り出し* インデックスに沿って行を取り出し* 5件データが揃ったらループから抜ける
WHEREと ORDER BYを両方カバーするキー
my $index_num = $country_index->{map}->{Asia}->{self};my $index_range= $country_index->{index}->[$index_num];
LOOP: foreach my $rownum_array (@$index_range){ foreach my $rownum (@$rownum_array) { my $row= $country_table->[$rownum];
printf("%s\t%s\t%d\n", $row->{name}, $row->{continent}, $row->{population}); if (++$count >= 5) {last LOOP;} }}
indexed_where_indexed_orderby.pl
WHEREと ORDER BYを両方カバーするキー
my $index_num = $country_index->{map}->{Asia}->{self};my $index_range= $country_index->{index}->[$index_num];
LOOP: foreach my $rownum_array (@$index_range){ foreach my $rownum (@$rownum_array) { my $row= $country_table->[$rownum];
printf("%s\t%s\t%d\n", $row->{name}, $row->{continent}, $row->{population}); if (++$count >= 5) {last LOOP;} }}
WHERE句のレンジをインデックスから取り出す
indexed_where_indexed_orderby.pl
WHEREと ORDER BYを両方カバーするキー
my $index_num = $country_index->{map}->{Asia}->{self};my $index_range= $country_index->{index}->[$index_num];
LOOP: foreach my $rownum_array (@$index_range){ foreach my $rownum (@$rownum_array) { my $row= $country_table->[$rownum];
printf("%s\t%s\t%d\n", $row->{name}, $row->{continent}, $row->{population}); if (++$count >= 5) {last LOOP;} }}
インデックスに沿って行を取り出して
indexed_where_indexed_orderby.pl
WHEREと ORDER BYを両方カバーするキー
my $index_num = $country_index->{map}->{Asia}->{self};my $index_range= $country_index->{index}->[$index_num];
LOOP: foreach my $rownum_array (@$index_range){ foreach my $rownum (@$rownum_array) { my $row= $country_table->[$rownum];
printf("%s\t%s\t%d\n", $row->{name}, $row->{continent}, $row->{population}); if (++$count >= 5) {last LOOP;} }}
先頭の5件を出力したらそこでループを抜ける
indexed_where_indexed_orderby.pl
というのを踏まえて
MySQLは JOINが遅い( キリッ
見ていきましょう
スキャンジョイン
mysql56> EXPLAIN SELECT Name, Language, Population, Percentage -> FROM Country INNER JOIN CountryLanguage ON Country.Code= CountryLanguage.CountryCode -> WHERE Country.continent = 'Asia' -> ORDER BY Percentage LIMIT 5;+----+-------------+-----------------+------+---------------+------+---------+------+------+----------------------------------------------------+| id | select_type | table | type | possible_keys | key | key_len | ref | rows | Extra |+----+-------------+-----------------+------+---------------+------+---------+------+------+----------------------------------------------------+| 1 | SIMPLE | CountryLanguage | ALL | NULL | NULL | NULL | NULL | 984 | Using temporary; Using filesort || 1 | SIMPLE | Country | ALL | NULL | NULL | NULL | NULL | 239 | Using where; Using join buffer (Block Nested Loop) |+----+-------------+-----------------+------+---------------+------+---------+------+------+----------------------------------------------------+2 rows in set (0.00 sec)
* 外側のテーブルから1行データをフェッチして* WHEREと ONのカラムで内側のテーブルを評価して ソートバッファに詰め込み* 外側のテーブルから次の1行をフェッチして…を繰り返し* クイックソートして先頭から5件取り出す
スキャンジョイン
for (my $language_rownum= 0; $language_rownum < scalar(@$language_table); $language_rownum++){ my $language_row= $language_table->[$language_rownum];
for (my $country_rownum= 0; $country_rownum < scalar(@$country_table); $country_rownum++) { my $country_row= $country_table->[$country_rownum];
$evaluted++; if ($language_row->{countrycode} eq $country_row->{code} && $country_row->{continent} eq "Asia") { $sorted++; push(@{$sort_buffer->{$language_row->{percentage}}}, [$language_rownum, $country_rownum]); } }}
ファイルソート以降は略
scan_join_scan_where_scan_orderby.pl
スキャンジョイン
for (my $language_rownum= 0; $language_rownum < scalar(@$language_table); $language_rownum++){ my $language_row= $language_table->[$language_rownum];
for (my $country_rownum= 0; $country_rownum < scalar(@$country_table); $country_rownum++) { my $country_row= $country_table->[$country_rownum];
$evaluted++; if ($language_row->{countrycode} eq $country_row->{code} && $country_row->{continent} eq "Asia") { $sorted++; push(@{$sort_buffer->{$language_row->{percentage}}}, [$language_rownum, $country_rownum]); } }}
ファイルソート以降は略
外側のテーブルから1行ずつフェッチ
scan_join_scan_where_scan_orderby.pl
スキャンジョイン
for (my $language_rownum= 0; $language_rownum < scalar(@$language_table); $language_rownum++){ my $language_row= $language_table->[$language_rownum];
for (my $country_rownum= 0; $country_rownum < scalar(@$country_table); $country_rownum++) { my $country_row= $country_table->[$country_rownum];
$evaluted++; if ($language_row->{countrycode} eq $country_row->{code} && $country_row->{continent} eq "Asia") { $sorted++; push(@{$sort_buffer->{$language_row->{percentage}}}, [$language_rownum, $country_rownum]); } }}
ファイルソート以降は略
scan_join_scan_where_scan_orderby.pl
内側のテーブルでも1行ずつフェッチして
スキャンジョイン
for (my $language_rownum= 0; $language_rownum < scalar(@$language_table); $language_rownum++){ my $language_row= $language_table->[$language_rownum];
for (my $country_rownum= 0; $country_rownum < scalar(@$country_table); $country_rownum++) { my $country_row= $country_table->[$country_rownum];
$evaluted++; if ($language_row->{countrycode} eq $country_row->{code} && $country_row->{continent} eq "Asia") { $sorted++; push(@{$sort_buffer->{$language_row->{percentage}}}, [$language_rownum, $country_rownum]); } }}
ファイルソート以降は略
scan_join_scan_where_scan_orderby.pl
評価&ソートバッファ
スキャンジョイン
$ ./scan_join_scan_where_scan_orderby.plUnited Arab Emirates Hindi 2441000 0.000000Bahrain English 617000 0.000000Japan Ainu 126714000 0.000000Kuwait English 1972000 0.000000Lebanon French 3282000 0.000000Total rows evaluted are 235176, sorted are 239.
評価する行の数が倍倍ゲェム
scan_join_scan_where_scan_orderby.pl
しねばいいのに
直感の赴くまま
SELECT Name, Language, Population, PercentageFROM Country INNER JOIN CountryLanguage ON Country.Code= CountryLanguage.CountryCodeWHERE Country.continent = 'Asia'ORDER BY CountryLanguage.Percentage LIMIT 5;
mysql56> ALTER TABLE Country ADD KEY index_continent(continent);Query OK, 0 rows affected (0.05 sec)Records: 0 Duplicates: 0 Warnings: 0
mysql56> ALTER TABLE CountryLanguage -> ADD KEY index_countrycode_percentage(countrycode, percentage);Query OK, 0 rows affected (0.11 sec)Records: 0 Duplicates: 0 Warnings: 0
これは残念ながらWHERE狙いのキーになる。わかりますん。
JOIN de WHERE狙いのキー
mysql56> EXPLAIN SELECT Name, Language, Population, Percentage -> FROM Country INNER JOIN CountryLanguage ON Country.Code= CountryLanguage.CountryCode -> WHERE Country.continent = 'Asia' -> ORDER BY Percentage LIMIT 5;+----+-------------+-----------------+------+------------------------------+------------------------------+---------+--------------------+------+--------------------------------------------------------+| id | select_type | table | type | possible_keys | key | key_len | ref | rows | Extra |+----+-------------+-----------------+------+------------------------------+------------------------------+---------+--------------------+------+--------------------------------------------------------+| 1 | SIMPLE | Country | ref | index_continent | index_continent | 33 | const | 51 | Using index condition; Using temporary; Using filesort || 1 | SIMPLE | CountryLanguage | ref | index_countrycode_percentage | index_countrycode_percentage | 3 | world.Country.Code | 2 | NULL |+----+-------------+-----------------+------+------------------------------+------------------------------+---------+--------------------+------+--------------------------------------------------------+2 rows in set (0.00 sec)
* 外側のテーブルをインデックスで刈り込んで* 内側のテーブルもインデックスで刈り込んで* ソートバッファに詰め込み* ループして* クイックソートして先頭から5件取り出す
JOIN de WHERE狙いのキー
my $country_index_num = $country_index->{map}->{Asia};my $country_rownum_array= $country_index->{index}->[$country_index_num];
foreach my $country_rownum (@$country_rownum_array){ my $country_row= $country_table->[$country_rownum];
$evaluted++; my $language_index_num = $language_index->{map}->{$country_row->{code}}->{self}; my $language_index_range= $language_index->{index}->[$language_index_num];
foreach my $language_rownum_array (@$language_index_range) { foreach my $language_rownum (@$language_rownum_array) { my $language_row= $language_table->[$language_rownum];
$sorted++; push(@{$sort_buffer->{$language_row->{percentage}}}, [$country_rownum, $language_rownum]); } }}
ファイルソート以降は略
indexed_join_indexed_where_scan_orderby.pl
JOIN de WHERE狙いのキー
my $country_index_num = $country_index->{map}->{Asia};my $country_rownum_array= $country_index->{index}->[$country_index_num];
foreach my $country_rownum (@$country_rownum_array){ my $country_row= $country_table->[$country_rownum];
$evaluted++; my $language_index_num = $language_index->{map}->{$country_row->{code}}->{self}; my $language_index_range= $language_index->{index}->[$language_index_num];
foreach my $language_rownum_array (@$language_index_range) { foreach my $language_rownum (@$language_rownum_array) { my $language_row= $language_table->[$language_rownum];
$sorted++; push(@{$sort_buffer->{$language_row->{percentage}}}, [$country_rownum, $language_rownum]); } }}
ファイルソート以降は略
外側のテーブルをWHEREで刈り込んで
indexed_join_indexed_where_scan_orderby.pl
JOIN de WHERE狙いのキーその1
my $country_index_num = $country_index->{map}->{Asia};my $country_rownum_array= $country_index->{index}->[$country_index_num];
foreach my $country_rownum (@$country_rownum_array){ my $country_row= $country_table->[$country_rownum];
$evaluted++; my $language_index_num = $language_index->{map}->{$country_row->{code}}->{self}; my $language_index_range= $language_index->{index}->[$language_index_num];
foreach my $language_rownum_array (@$language_index_range) { foreach my $language_rownum (@$language_rownum_array) { my $language_row= $language_table->[$language_rownum];
$sorted++; push(@{$sort_buffer->{$language_row->{percentage}}}, [$country_rownum, $language_rownum]); } }}
ファイルソート以降は略
刈り込んだ外側を1行ずつキーにして
内側テーブルを刈り込む
indexed_join_indexed_where_scan_orderby.pl
JOIN de WHERE狙いのキー
my $country_index_num = $country_index->{map}->{Asia};my $country_rownum_array= $country_index->{index}->[$country_index_num];
foreach my $country_rownum (@$country_rownum_array){ my $country_row= $country_table->[$country_rownum];
$evaluted++; my $language_index_num = $language_index->{map}->{$country_row->{code}}->{self}; my $language_index_range= $language_index->{index}->[$language_index_num];
foreach my $language_rownum_array (@$language_index_range) { foreach my $language_rownum (@$language_rownum_array) { my $language_row= $language_table->[$language_rownum];
$sorted++; push(@{$sort_buffer->{$language_row->{percentage}}}, [$country_rownum, $language_rownum]); } }}
ファイルソート以降は略
内側まで刈り込んだらソートバッファに詰める
indexed_join_indexed_where_scan_orderby.pl
JOIN de WHERE狙いのキー
$ ./indexed_join_indexed_where_scan_orderby.plUnited Arab Emirates Hindi 2441000 0.000000Bahrain English 617000 0.000000Japan Ainu 126714000 0.000000Kuwait English 1972000 0.000000Lebanon French 3282000 0.000000Total rows evaluted are 52, sorted are 239.
スキャンジョインよりよっぽどいいけどそんなに効率が良いわけではなさそう
indexed_join_indexed_where_scan_orderby.pl
percentageをインデックスに入れてるのにWHERE狙いのキー?
JOIN de WHERE狙いのキーmysql56> EXPLAIN SELECT Name, Language, Population, Percentage -> FROM Country INNER JOIN CountryLanguage ON Country.Code= CountryLanguage.CountryCode -> WHERE Country.continent = 'Asia' -> ORDER BY Percentage LIMIT 5;+----+-------------+-----------------+------+---------------+------+---------+------+------+----------------------------------------------------+| id | select_type | table | type | possible_keys | key | key_len | ref | rows | Extra |+----+-------------+-----------------+------+---------------+------+---------+------+------+----------------------------------------------------+| 1 | SIMPLE | CountryLanguage | ALL | NULL | NULL | NULL | NULL | 984 | Using temporary; Using filesort || 1 | SIMPLE | Country | ALL | NULL | NULL | NULL | NULL | 239 | Using where; Using join buffer (Block Nested Loop) |+----+-------------+-----------------+------+---------------+------+---------+------+------+----------------------------------------------------+2 rows in set (0.00 sec)
mysql56> EXPLAIN SELECT Name, Language, Population, Percentage -> FROM Country INNER JOIN CountryLanguage ON Country.Code= CountryLanguage.CountryCode -> WHERE Country.continent = 'Asia' -> ORDER BY Percentage LIMIT 5;+----+-------------+-----------------+------+------------------------------+------------------------------+---------+--------------------+------+--------------------------------------------------------+| id | select_type | table | type | possible_keys | key | key_len | ref | rows | Extra |+----+-------------+-----------------+------+------------------------------+------------------------------+---------+--------------------+------+--------------------------------------------------------+| 1 | SIMPLE | Country | ref | index_continent | index_continent | 33 | const | 51 | Using index condition; Using temporary; Using filesort || 1 | SIMPLE | CountryLanguage | ref | index_countrycode_percentage | index_countrycode_percentage | 3 | world.Country.Code | 2 | NULL |+----+-------------+-----------------+------+------------------------------+------------------------------+---------+--------------------+------+--------------------------------------------------------+2 rows in set (0.00 sec)
* 外部表と内部表が入れ替わってる* ORDER BYをインデックスで解決するには、 そのカラムが外部表にあることが必要
Let's プリントデバッグ
JOIN de WHERE狙いのキー
foreach my $country_rownum (@$country_rownum_array){ my $country_row= $country_table->[$country_rownum];
$evaluted++; my $language_index_num = $language_index->{map}->{$country_row->{code}}->{self}; my $language_index_range= $language_index->{index}->[$language_index_num];
foreach my $language_rownum_array (@$language_index_range) { foreach my $language_rownum (@$language_rownum_array) { my $language_row= $language_table->[$language_rownum];
$sorted++; printf("* %s\t%d\t%s => %s\t%f\n", $country_row->{name}, $country_row->{population}, $country_row->{code}, $language_row->{language}, $language_row->{percentage}); } }}
verbose_indexed_join_indexed_where_scan_orderby.pl
JOIN de WHERE狙いのキー
* Afghanistan 22720000 AFG => AFG Balochi 0.900000* Afghanistan 22720000 AFG => AFG Turkmenian 1.900000* Afghanistan 22720000 AFG => AFG Uzbek 8.800000* Afghanistan 22720000 AFG => AFG Dari 32.100000* Afghanistan 22720000 AFG => AFG Pashto 52.400000* United Arab Emirates 2441000 ARE => ARE Hindi 0.000000* United Arab Emirates 2441000 ARE => ARE Arabic 42.000000* Armenia 3520000 ARM => ARM Azerbaijani 2.600000* Armenia 3520000 ARM => ARM Armenian 93.400000* Azerbaijan 7734000 AZE => AZE Armenian 2.000000* Azerbaijan 7734000 AZE => AZE Lezgian 2.300000* Azerbaijan 7734000 AZE => AZE Russian 3.000000* Azerbaijan 7734000 AZE => AZE Azerbaijani 89.000000* Bangladesh 129155000 BGD => BGD Garo 0.100000* Bangladesh 129155000 BGD => BGD Khasi 0.100000* Bangladesh 129155000 BGD => BGD Santhali 0.100000* Bangladesh 129155000 BGD => BGD Tripuri 0.100000* Bangladesh 129155000 BGD => BGD Marma 0.200000* Bangladesh 129155000 BGD => BGD Chakma 0.400000* Bangladesh 129155000 BGD => BGD Bengali 97.700000* Bahrain 617000 BHR => BHR English 0.000000* Bahrain 617000 BHR => BHR Arabic 67.700000* Brunei 328000 BRN => BRN English 3.100000* Brunei 328000 BRN => BRN Chinese 9.300000* Brunei 328000 BRN => BRN Malay-English 28.800000* Brunei 328000 BRN => BRN Malay 45.500000..
verbose_indexed_join_indexed_where_scan_orderby.pl
* Afghanistan 22720000 AFG => AFG Balochi 0.900000* Afghanistan 22720000 AFG => AFG Turkmenian 1.900000* Afghanistan 22720000 AFG => AFG Uzbek 8.800000* Afghanistan 22720000 AFG => AFG Dari 32.100000* Afghanistan 22720000 AFG => AFG Pashto 52.400000* United Arab Emirates 2441000 ARE => ARE Hindi 0.000000* United Arab Emirates 2441000 ARE => ARE Arabic 42.000000* Armenia 3520000 ARM => ARM Azerbaijani 2.600000* Armenia 3520000 ARM => ARM Armenian 93.400000* Azerbaijan 7734000 AZE => AZE Armenian 2.000000* Azerbaijan 7734000 AZE => AZE Lezgian 2.300000* Azerbaijan 7734000 AZE => AZE Russian 3.000000* Azerbaijan 7734000 AZE => AZE Azerbaijani 89.000000* Bangladesh 129155000 BGD => BGD Garo 0.100000* Bangladesh 129155000 BGD => BGD Khasi 0.100000* Bangladesh 129155000 BGD => BGD Santhali 0.100000* Bangladesh 129155000 BGD => BGD Tripuri 0.100000* Bangladesh 129155000 BGD => BGD Marma 0.200000* Bangladesh 129155000 BGD => BGD Chakma 0.400000* Bangladesh 129155000 BGD => BGD Bengali 97.700000* Bahrain 617000 BHR => BHR English 0.000000* Bahrain 617000 BHR => BHR Arabic 67.700000* Brunei 328000 BRN => BRN English 3.100000* Brunei 328000 BRN => BRN Chinese 9.300000* Brunei 328000 BRN => BRN Malay-English 28.800000* Brunei 328000 BRN => BRN Malay 45.500000..
JOIN de WHERE狙いのキー
verbose_indexed_join_indexed_where_scan_orderby.pl
こっちがCountryこっちがCountryLanguage
確かにソートが必要になる
ORDER BY狙いのキーを使うにはどちらが外部表で
どう結合されるかをイメージ
JOIN de WHERE狙いのキー
* 0.000000 Hindi ARE => ARE United Arab Emirates 2441000* 0.000000 English BHR => BHR Bahrain 617000* 0.000000 Ainu JPN => JPN Japan 126714000* 0.000000 English KWT => KWT Kuwait 1972000* 0.000000 French LBN => LBN Lebanon 3282000* 0.000000 English MDV => MDV Maldives 286000* 0.000000 Balochi OMN => OMN Oman 2542000* 0.000000 Urdu QAT => QAT Qatar 599000* 0.000000 Portuguese TMP => TMP East Timor 885000* 0.000000 Sunda TMP => TMP East Timor 885000* 0.000000 Soqutri YEM => YEM Yemen 18112000* 0.100000 Garo BGD => BGD Bangladesh 129155000* 0.100000 Khasi BGD => BGD Bangladesh 129155000* 0.100000 Santhali BGD => BGD Bangladesh 129155000* 0.100000 Tripuri BGD => BGD Bangladesh 129155000* 0.100000 English JPN => JPN Japan 126714000* 0.100000 Philippene Languages JPN => JPN Japan 126714000* 0.100000 Chinese KOR => KOR South Korea 46844000* 0.100000 Chinese PRK => PRK North Korea 24039000* 0.200000 Marma BGD => BGD Bangladesh 129155000* 0.200000 Dong CHN => CHN China 1277558000* 0.200000 Puyi CHN => CHN China 1277558000* 0.200000 Chinese JPN => JPN Japan 126714000* 0.300000 Paiwan TWN => TWN Taiwan 22256000* 0.400000 Chakma BGD => BGD Bangladesh 129155000* 0.400000 Mongolian CHN => CHN China 1277558000..
verbose_indexed_join_indexed_where_indexed_orderby.pl
こっちがCountry
こっちがCountryLanguage
JOIN de WHERE狙いのキー
* 0.000000 Hindi ARE => ARE United Arab Emirates 2441000* 0.000000 English BHR => BHR Bahrain 617000* 0.000000 Ainu JPN => JPN Japan 126714000* 0.000000 English KWT => KWT Kuwait 1972000* 0.000000 French LBN => LBN Lebanon 3282000* 0.000000 English MDV => MDV Maldives 286000* 0.000000 Balochi OMN => OMN Oman 2542000* 0.000000 Urdu QAT => QAT Qatar 599000* 0.000000 Portuguese TMP => TMP East Timor 885000* 0.000000 Sunda TMP => TMP East Timor 885000* 0.000000 Soqutri YEM => YEM Yemen 18112000* 0.100000 Garo BGD => BGD Bangladesh 129155000* 0.100000 Khasi BGD => BGD Bangladesh 129155000* 0.100000 Santhali BGD => BGD Bangladesh 129155000* 0.100000 Tripuri BGD => BGD Bangladesh 129155000* 0.100000 English JPN => JPN Japan 126714000* 0.100000 Philippene Languages JPN => JPN Japan 126714000* 0.100000 Chinese KOR => KOR South Korea 46844000* 0.100000 Chinese PRK => PRK North Korea 24039000* 0.200000 Marma BGD => BGD Bangladesh 129155000* 0.200000 Dong CHN => CHN China 1277558000* 0.200000 Puyi CHN => CHN China 1277558000* 0.200000 Chinese JPN => JPN Japan 126714000* 0.300000 Paiwan TWN => TWN Taiwan 22256000* 0.400000 Chakma BGD => BGD Bangladesh 129155000* 0.400000 Mongolian CHN => CHN China 1277558000..
verbose_indexed_join_indexed_where_indexed_orderby.pl
ここでソート済みであってほしいから
こっち向きに結合してほしい
JOIN de ORDER BY狙いのキー
mysql56> ALTER TABLE Country ADD KEY index_code_continent(code, continent);Query OK, 0 rows affected (0.06 sec)Records: 0 Duplicates: 0 Warnings: 0
mysql56> ALTER TABLE CountryLanguage ADD KEY index_percentage(percentage);Query OK, 0 rows affected (0.07 sec)Records: 0 Duplicates: 0 Warnings: 0
mysql56> EXPLAIN SELECT Name, Language, Population, Percentage -> FROM CountryLanguage JOIN Country ON Country.Code= CountryLanguage.CountryCode -> WHERE Country.continent = 'Asia' -> ORDER BY Percentage LIMIT 5;+----+-------------+-----------------+-------+----------------------+----------------------+---------+-----------------------------------------+------+-----------------------+| id | select_type | table | type | possible_keys | key | key_len | ref | rows | Extra |+----+-------------+-----------------+-------+----------------------+----------------------+---------+-----------------------------------------+------+-----------------------+| 1 | SIMPLE | CountryLanguage | index | NULL | index_percentage | 4 | NULL | 5 | NULL || 1 | SIMPLE | Country | ref | index_code_continent | index_code_continent | 36 | world.CountryLanguage.CountryCode,const | 1 | Using index condition |+----+-------------+-----------------+-------+----------------------+----------------------+---------+-----------------------------------------+------+-----------------------+2 rows in set (0.00 sec)
* 外側のテーブルはORDER BY狙いのキー** ↑はただの JOINなれど、STRAIGHT_JOINした方がいい。* 1行ごとにWHEREと ONのカラムで評価* 合計 5行マッチしたら終了
JOIN de ORDER BY狙いのキー
my $language_cardinality= scalar(keys(%{$language_index->{map}}));LOOP: for (my $language_index_num= 0; $language_index_num < $language_cardinality; $language_index_num++){ my $language_rownum_array= $language_index->{index}->[$language_index_num]; foreach my $language_rownum (@$language_rownum_array) { my $language_row= $language_table->[$language_rownum]; my $country_index_num_first= $country_index->{map}->{$language_row->{countrycode}}->{self}; my $country_index_num_second= $country_index->{map}->{$language_row->{countrycode}}->{Asia};
if (!(defined($country_index_num_second))) {next;}
my $country_rownum_array= $country_index->{index}->[$country_index_num_first]->[$country_index_num_second];
foreach my $country_rownum (@$country_rownum_array) { my $country_row= $country_table->[$country_rownum];
printf("%s\t%s\t%d\t%f\n", $country_row->{name}, $language_row->{language}, $country_row->{population}, $language_row->{percentage}); if (++$count >= 5) {last LOOP;} } }}
indexed_join_indexed_where_indexed_orderby.pl
JOIN de ORDER BY狙いのキー
my $language_cardinality= scalar(keys(%{$language_index->{map}}));LOOP: for (my $language_index_num= 0; $language_index_num < $language_cardinality; $language_index_num++){ my $language_rownum_array= $language_index->{index}->[$language_index_num]; foreach my $language_rownum (@$language_rownum_array) { my $language_row= $language_table->[$language_rownum]; my $country_index_num_first= $country_index->{map}->{$language_row->{countrycode}}->{self}; my $country_index_num_second= $country_index->{map}->{$language_row->{countrycode}}->{Asia};
if (!(defined($country_index_num_second))) {next;}
my $country_rownum_array= $country_index->{index}->[$country_index_num_first]->[$country_index_num_second];
foreach my $country_rownum (@$country_rownum_array) { my $country_row= $country_table->[$country_rownum];
printf("%s\t%s\t%d\t%f\n", $country_row->{name}, $language_row->{language}, $country_row->{population}, $language_row->{percentage}); if (++$count >= 5) {last LOOP;} } }}
外側のテーブルにはORDER BY狙いのキー
indexed_join_indexed_where_indexed_orderby.pl
JOIN de ORDER BY狙いのキー
my $language_cardinality= scalar(keys(%{$language_index->{map}}));LOOP: for (my $language_index_num= 0; $language_index_num < $language_cardinality; $language_index_num++){ my $language_rownum_array= $language_index->{index}->[$language_index_num]; foreach my $language_rownum (@$language_rownum_array) { my $language_row= $language_table->[$language_rownum]; my $country_index_num_first= $country_index->{map}->{$language_row->{countrycode}}->{self}; my $country_index_num_second= $country_index->{map}->{$language_row->{countrycode}}->{Asia};
if (!(defined($country_index_num_second))) {next;}
my $country_rownum_array= $country_index->{index}->[$country_index_num_first]->[$country_index_num_second];
foreach my $country_rownum (@$country_rownum_array) { my $country_row= $country_table->[$country_rownum];
printf("%s\t%s\t%d\t%f\n", $country_row->{name}, $language_row->{language}, $country_row->{population}, $language_row->{percentage}); if (++$count >= 5) {last LOOP;} } }}
内側のテーブルはWHERE狙いのキー
indexed_join_indexed_where_indexed_orderby.pl
WHERE狙いのキーORDER BY狙いのキー
● もちろん両方狙えるインデックスを作っていくのが最良。● ただし必ずしも一番良いキーでWHEREと ORDER BYを両方狙い打ちできるとは限らない。
● どっちがどういう動作になるかがイメージできれば、打ち分けるのはそう難しくない
● ちゃんと狙ったクエリーとそうでないクエリーは100倍くらい性能が違うこともザラ。
まとめ
● WHERE句で十分絞り込める場合はWHERE狙い● たとえば WHERE Continent = 'Moo'(マッチ0件 )だったらWHERE狙い。ORDER BY狙いだと5件揃わないのでテーブルをまるまるスキャンする。
● WHERE句がほとんど機能しないような場合はORDER BY狙い● 同じカラム同じカーディナリティーでも、値によって本当は違う。
● このセッションを聞いている人をWHERE gender = 'male'ならORDER BYを狙った方がいいだろうし、WHERE gender = 'female'ならWHEREを狙った方がいい。
愚痴
● 本当はそのあたりの按配をオプティマイザー氏が上手くやってくれると嬉しい。● Oracle DBは上手くやってくれるらしい(って聞いた。ホントかどうかは知らない)
● MySQLのオプティマイザーが残念なのはInnoDBの統計情報のせいもある。– InnoDBの統計情報はサンプリング(デフォルトでは1インデックスあたり8ページ)
● 8ページ= 128kB、テーブルサイズがどれだけあろうとも。
– 俺たちが「 MySQLのオプティマイザー氏はバカ」なんて言ってられるのは、「そのカラムの偏りが(設計上やサービス上)どうなりそうか」という高精度な予測を持っているから。
「何とかとMySQLは使いよう」
楽しいMySQLライフを!