SlideShare a Scribd company logo
1 of 109
Download to read offline
WHERE狙いのキー、 
ORDER BY狙いのキー 
2014/08/28 
yoku0825 
YAPC::Asia Tokyo 2014
みんなEXPLAINしてるー? 
目XPLAINでも可
type: ALL
邪悪ですね
Extra: Using where; Using 
filesort
うーん…
でもなんで? 
クエリーが遅い時にこいつらが出るのか、 
こいつらが出る時にクエリーが遅くなるのか
という話を 
コードで例えて 
お話ししたいと思います 
Perlがへたっぴなのは 
大目にみてくだしあ。。
今日の意気込み
Kuso-Query 
As 
AC 
ode
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 cards 
WHERE 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,9 
6,100 
diamond 1,5,15,23,24,26,32,33,41,45,48,49,5 
1,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,9 
7,99
これがWHERE句を部分的に 
インデックスで解決したパターン 
SELECT * FROM cards 
WHERE 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 cards 
WHERE mark= 'spade' AND number= 'A';
このチートシートを使っていいから、 
山の中からハートを 
KからAに向かって並べておくれ 
SELECT * FROM cards 
WHERE 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', 
.. 
}, 
.. 
hashrefを要素にした 
arrayrefで表現 
display_table_structure.pl
テーブルデータ 
$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("%st%st%dn", 
$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("%st%st%dn", 
$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("%st%st%dn", 
$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("%st%st%dn", 
$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("%st%st%dn", 
$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("%st%st%dn", 
$row->{name}, 
$row->{continent}, 
$row->{population}); 
if (++$count >= 5) 
{last;} 
} 
先頭の5件を出力したら 
そこでループを抜ける 
scan_where_scan_orderby.pl
テーブルスキャン 
$ ./scan_where_scan_orderby.pl 
Maldives Asia 286000 
Brunei Asia 328000 
Macao Asia 473000 
Qatar Asia 599000 
Bahrain Asia 617000 
Total 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, 
.., 
Continent = 'Africa'な 
行IDを取り出すには 
$index->[0]->{Africa} 
(arrayrefとして取り出す) 
display_single_column_index_structure.pl
インデックス 
$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}に押し込めた 
こっちはarrayrefのarrayrefに 
display_single_column_index_structure.pl
インデックス 
$index_num= $index->{map}->{Africa} 
$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 
$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("%st%st%dn", 
$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("%st%st%dn", 
$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("%st%st%dn", 
$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("%st%st%dn", 
$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("%st%st%dn", 
$row->{name}, 
$row->{continent}, 
$row->{population}); 
if (++$count >= 5) 
{last;} 
} 
先頭の5件を出力したら 
ループを抜ける 
indexed_where_scan_orderby.pl
WHERE狙いのキー 
$ ./indexed_where_scan_orderby.pl 
Maldives Asia 286000 
Brunei Asia 328000 
Macao Asia 473000 
Qatar Asia 599000 
Bahrain Asia 617000 
Total rows evaluted are 1, sorted are 51. 
WHEREの評価は1回で済むけど 
indexed_where_scan_orderby.pl 
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("%st%st%dn", 
$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("%st%st%dn", 
$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("%st%st%dn", 
$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("%st%st%dn", 
$row->{name}, 
$row->{continent}, 
$row->{population}); 
if (++$count >= 5) 
{last LOOP;} 
} 
} 
} 
先頭の5件を出力したら 
そこでループを抜ける 
scan_where_indexed_orderby.pl
ORDER BY狙いのキー 
$ ./scan_where_indexed_orderby.pl 
Maldives Asia 286000 
Brunei Asia 328000 
Macao Asia 473000 
Qatar Asia 599000 
Bahrain Asia 617000 
Total 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 
], 
.. 
マジックナンバーよけをしたら 
かなりカオス 
selfを予約語にしてるのが 
ものすごくイケてない 
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 
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' => { 
my $index_nums_hash= 
$index->{map}->{Oceania}->{self}; 
'154000' => 14, 
'21000' => 4, 
.. 
}, 
} 
}, 
'index' => [ 
[ 
[ 
100 
], 
[ 
188 
], 
.. 
display_double_column_index_structure.pl 
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("%st%st%dn", 
$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("%st%st%dn", 
$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("%st%st%dn", 
$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("%st%st%dn", 
$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]); 
} 
} 
} 
ファイルソート以降は略 
内側のテーブルでも 
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
スキャンジョイン 
$ ./scan_join_scan_where_scan_orderby.pl 
United Arab Emirates Hindi 2441000 0.000000 
Bahrain English 617000 0.000000 
Japan Ainu 126714000 0.000000 
Kuwait English 1972000 0.000000 
Lebanon French 3282000 0.000000 
Total rows evaluted are 235176, sorted are 239. 
評価する行の数が倍倍ゲェム 
scan_join_scan_where_scan_orderby.pl
しねばいいのに
直感の赴くまま 
SELECT Name, Language, Population, Percentage 
FROM Country INNER JOIN CountryLanguage ON Country.Code= CountryLanguage.CountryCode 
WHERE 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.pl 
United Arab Emirates Hindi 2441000 0.000000 
Bahrain English 617000 0.000000 
Japan Ainu 126714000 0.000000 
Kuwait English 1972000 0.000000 
Lebanon French 3282000 0.000000 
Total 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.Co 
untry.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("* %st%dt%s => %st%fn", 
$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
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 
こっちが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 
.. 
こっちがCountry 
verbose_indexed_join_indexed_where_indexed_orderby.pl 
こっちが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("%st%st%dt%fn", 
$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("%st%st%dt%fn", 
$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("%st%st%dt%fn", 
$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ライフを!

More Related Content

What's hot

イミュータブルデータモデルの極意
イミュータブルデータモデルの極意イミュータブルデータモデルの極意
イミュータブルデータモデルの極意Yoshitaka Kawashima
 
雑なMySQLパフォーマンスチューニング
雑なMySQLパフォーマンスチューニング雑なMySQLパフォーマンスチューニング
雑なMySQLパフォーマンスチューニングyoku0825
 
Redisの特徴と活用方法について
Redisの特徴と活用方法についてRedisの特徴と活用方法について
Redisの特徴と活用方法についてYuji Otani
 
MySQL 5.7にやられないためにおぼえておいてほしいこと
MySQL 5.7にやられないためにおぼえておいてほしいことMySQL 5.7にやられないためにおぼえておいてほしいこと
MySQL 5.7にやられないためにおぼえておいてほしいことyoku0825
 
SQLアンチパターン~スパゲッティクエリ
SQLアンチパターン~スパゲッティクエリSQLアンチパターン~スパゲッティクエリ
SQLアンチパターン~スパゲッティクエリItabashi Masayuki
 
DDD x CQRS 更新系と参照系で異なるORMを併用して上手くいった話
DDD x CQRS   更新系と参照系で異なるORMを併用して上手くいった話DDD x CQRS   更新系と参照系で異なるORMを併用して上手くいった話
DDD x CQRS 更新系と参照系で異なるORMを併用して上手くいった話Koichiro Matsuoka
 
基本に戻ってInnoDBの話をします
基本に戻ってInnoDBの話をします基本に戻ってInnoDBの話をします
基本に戻ってInnoDBの話をしますyoku0825
 
今からでも遅くないDBマイグレーション - Flyway と SchemaSpy の紹介 -
今からでも遅くないDBマイグレーション - Flyway と SchemaSpy の紹介 -今からでも遅くないDBマイグレーション - Flyway と SchemaSpy の紹介 -
今からでも遅くないDBマイグレーション - Flyway と SchemaSpy の紹介 -onozaty
 
マイクロにしすぎた結果がこれだよ!
マイクロにしすぎた結果がこれだよ!マイクロにしすぎた結果がこれだよ!
マイクロにしすぎた結果がこれだよ!mosa siru
 
SQLチューニング入門 入門編
SQLチューニング入門 入門編SQLチューニング入門 入門編
SQLチューニング入門 入門編Miki Shimogai
 
MySQLアーキテクチャ図解講座
MySQLアーキテクチャ図解講座MySQLアーキテクチャ図解講座
MySQLアーキテクチャ図解講座Mikiya Okuno
 
Java ORマッパー選定のポイント #jsug
Java ORマッパー選定のポイント #jsugJava ORマッパー選定のポイント #jsug
Java ORマッパー選定のポイント #jsugMasatoshi Tada
 
Goのサーバサイド実装におけるレイヤ設計とレイヤ内実装について考える
Goのサーバサイド実装におけるレイヤ設計とレイヤ内実装について考えるGoのサーバサイド実装におけるレイヤ設計とレイヤ内実装について考える
Goのサーバサイド実装におけるレイヤ設計とレイヤ内実装について考えるpospome
 
ドメイン駆動設計のためのオブジェクト指向入門
ドメイン駆動設計のためのオブジェクト指向入門ドメイン駆動設計のためのオブジェクト指向入門
ドメイン駆動設計のためのオブジェクト指向入門増田 亨
 
本当は恐ろしい分散システムの話
本当は恐ろしい分散システムの話本当は恐ろしい分散システムの話
本当は恐ろしい分散システムの話Kumazaki Hiroki
 
MySQLアンチパターン
MySQLアンチパターンMySQLアンチパターン
MySQLアンチパターンyoku0825
 
ドメイン駆動設計のための Spring の上手な使い方
ドメイン駆動設計のための Spring の上手な使い方ドメイン駆動設計のための Spring の上手な使い方
ドメイン駆動設計のための Spring の上手な使い方増田 亨
 

What's hot (20)

イミュータブルデータモデルの極意
イミュータブルデータモデルの極意イミュータブルデータモデルの極意
イミュータブルデータモデルの極意
 
雑なMySQLパフォーマンスチューニング
雑なMySQLパフォーマンスチューニング雑なMySQLパフォーマンスチューニング
雑なMySQLパフォーマンスチューニング
 
ヤフー社内でやってるMySQLチューニングセミナー大公開
ヤフー社内でやってるMySQLチューニングセミナー大公開ヤフー社内でやってるMySQLチューニングセミナー大公開
ヤフー社内でやってるMySQLチューニングセミナー大公開
 
Vacuum徹底解説
Vacuum徹底解説Vacuum徹底解説
Vacuum徹底解説
 
Redisの特徴と活用方法について
Redisの特徴と活用方法についてRedisの特徴と活用方法について
Redisの特徴と活用方法について
 
MySQL 5.7にやられないためにおぼえておいてほしいこと
MySQL 5.7にやられないためにおぼえておいてほしいことMySQL 5.7にやられないためにおぼえておいてほしいこと
MySQL 5.7にやられないためにおぼえておいてほしいこと
 
SQLアンチパターン~スパゲッティクエリ
SQLアンチパターン~スパゲッティクエリSQLアンチパターン~スパゲッティクエリ
SQLアンチパターン~スパゲッティクエリ
 
DDD x CQRS 更新系と参照系で異なるORMを併用して上手くいった話
DDD x CQRS   更新系と参照系で異なるORMを併用して上手くいった話DDD x CQRS   更新系と参照系で異なるORMを併用して上手くいった話
DDD x CQRS 更新系と参照系で異なるORMを併用して上手くいった話
 
基本に戻ってInnoDBの話をします
基本に戻ってInnoDBの話をします基本に戻ってInnoDBの話をします
基本に戻ってInnoDBの話をします
 
今からでも遅くないDBマイグレーション - Flyway と SchemaSpy の紹介 -
今からでも遅くないDBマイグレーション - Flyway と SchemaSpy の紹介 -今からでも遅くないDBマイグレーション - Flyway と SchemaSpy の紹介 -
今からでも遅くないDBマイグレーション - Flyway と SchemaSpy の紹介 -
 
マイクロにしすぎた結果がこれだよ!
マイクロにしすぎた結果がこれだよ!マイクロにしすぎた結果がこれだよ!
マイクロにしすぎた結果がこれだよ!
 
SQLチューニング入門 入門編
SQLチューニング入門 入門編SQLチューニング入門 入門編
SQLチューニング入門 入門編
 
MySQLアーキテクチャ図解講座
MySQLアーキテクチャ図解講座MySQLアーキテクチャ図解講座
MySQLアーキテクチャ図解講座
 
Java ORマッパー選定のポイント #jsug
Java ORマッパー選定のポイント #jsugJava ORマッパー選定のポイント #jsug
Java ORマッパー選定のポイント #jsug
 
Goのサーバサイド実装におけるレイヤ設計とレイヤ内実装について考える
Goのサーバサイド実装におけるレイヤ設計とレイヤ内実装について考えるGoのサーバサイド実装におけるレイヤ設計とレイヤ内実装について考える
Goのサーバサイド実装におけるレイヤ設計とレイヤ内実装について考える
 
ドメイン駆動設計のためのオブジェクト指向入門
ドメイン駆動設計のためのオブジェクト指向入門ドメイン駆動設計のためのオブジェクト指向入門
ドメイン駆動設計のためのオブジェクト指向入門
 
本当は恐ろしい分散システムの話
本当は恐ろしい分散システムの話本当は恐ろしい分散システムの話
本当は恐ろしい分散システムの話
 
MySQLアンチパターン
MySQLアンチパターンMySQLアンチパターン
MySQLアンチパターン
 
ドメイン駆動設計のための Spring の上手な使い方
ドメイン駆動設計のための Spring の上手な使い方ドメイン駆動設計のための Spring の上手な使い方
ドメイン駆動設計のための Spring の上手な使い方
 
At least onceってぶっちゃけ問題の先送りだったよね #kafkajp
At least onceってぶっちゃけ問題の先送りだったよね #kafkajpAt least onceってぶっちゃけ問題の先送りだったよね #kafkajp
At least onceってぶっちゃけ問題の先送りだったよね #kafkajp
 

Similar to Where狙いのキー、order by狙いのキー

Hachiojipm #5 LT資料 [テーマ:最近いいなと思ったもの]
Hachiojipm #5 LT資料 [テーマ:最近いいなと思ったもの] Hachiojipm #5 LT資料 [テーマ:最近いいなと思ったもの]
Hachiojipm #5 LT資料 [テーマ:最近いいなと思ったもの] norry_gogo
 
知ってるようで意外と知らないPHPの便利関数
知ってるようで意外と知らないPHPの便利関数知ってるようで意外と知らないPHPの便利関数
知ってるようで意外と知らないPHPの便利関数Wataru Terada
 
PHP foreachでの参照渡しに潜む罠
PHP foreachでの参照渡しに潜む罠PHP foreachでの参照渡しに潜む罠
PHP foreachでの参照渡しに潜む罠Takaaki Hirano
 
Perlで伝統芸能
Perlで伝統芸能Perlで伝統芸能
Perlで伝統芸能hitode909
 
Yapc -asia 2012 lt @studio3104
Yapc -asia 2012 lt @studio3104Yapc -asia 2012 lt @studio3104
Yapc -asia 2012 lt @studio3104Satoshi Suzuki
 
本当は怖いPHP
本当は怖いPHP本当は怖いPHP
本当は怖いPHPTakuya Sato
 
詳説ぺちぺち
詳説ぺちぺち詳説ぺちぺち
詳説ぺちぺちdo_aki
 
F#入門 ~関数プログラミングとは何か~
F#入門 ~関数プログラミングとは何か~F#入門 ~関数プログラミングとは何か~
F#入門 ~関数プログラミングとは何か~Nobuhisa Koizumi
 
Perl Hobby Programming - Games::BeLike::EightBIT ターミナルで8ビット風ゲームをつくろう
Perl Hobby Programming - Games::BeLike::EightBIT ターミナルで8ビット風ゲームをつくろうPerl Hobby Programming - Games::BeLike::EightBIT ターミナルで8ビット風ゲームをつくろう
Perl Hobby Programming - Games::BeLike::EightBIT ターミナルで8ビット風ゲームをつくろうkeroyonn
 
Livesense tech night immutable-js at a glance
Livesense tech night   immutable-js at a glanceLivesense tech night   immutable-js at a glance
Livesense tech night immutable-js at a glanceYuta Shimakawa
 

Similar to Where狙いのキー、order by狙いのキー (13)

Hachiojipm #5 LT資料 [テーマ:最近いいなと思ったもの]
Hachiojipm #5 LT資料 [テーマ:最近いいなと思ったもの] Hachiojipm #5 LT資料 [テーマ:最近いいなと思ったもの]
Hachiojipm #5 LT資料 [テーマ:最近いいなと思ったもの]
 
知ってるようで意外と知らないPHPの便利関数
知ってるようで意外と知らないPHPの便利関数知ってるようで意外と知らないPHPの便利関数
知ってるようで意外と知らないPHPの便利関数
 
PHP foreachでの参照渡しに潜む罠
PHP foreachでの参照渡しに潜む罠PHP foreachでの参照渡しに潜む罠
PHP foreachでの参照渡しに潜む罠
 
Perlで伝統芸能
Perlで伝統芸能Perlで伝統芸能
Perlで伝統芸能
 
数数
 
Yapc -asia 2012 lt @studio3104
Yapc -asia 2012 lt @studio3104Yapc -asia 2012 lt @studio3104
Yapc -asia 2012 lt @studio3104
 
本当は怖いPHP
本当は怖いPHP本当は怖いPHP
本当は怖いPHP
 
詳説ぺちぺち
詳説ぺちぺち詳説ぺちぺち
詳説ぺちぺち
 
F#入門 ~関数プログラミングとは何か~
F#入門 ~関数プログラミングとは何か~F#入門 ~関数プログラミングとは何か~
F#入門 ~関数プログラミングとは何か~
 
Perl for visualization
Perl for visualizationPerl for visualization
Perl for visualization
 
Perl Hobby Programming - Games::BeLike::EightBIT ターミナルで8ビット風ゲームをつくろう
Perl Hobby Programming - Games::BeLike::EightBIT ターミナルで8ビット風ゲームをつくろうPerl Hobby Programming - Games::BeLike::EightBIT ターミナルで8ビット風ゲームをつくろう
Perl Hobby Programming - Games::BeLike::EightBIT ターミナルで8ビット風ゲームをつくろう
 
Livesense tech night immutable-js at a glance
Livesense tech night   immutable-js at a glanceLivesense tech night   immutable-js at a glance
Livesense tech night immutable-js at a glance
 
Data munging
Data mungingData munging
Data munging
 

More from yoku0825

逝くぞ最新版、罠の貯蔵は十分か
逝くぞ最新版、罠の貯蔵は十分か逝くぞ最新版、罠の貯蔵は十分か
逝くぞ最新版、罠の貯蔵は十分かyoku0825
 
サーバーが完膚なきまでに死んでもMySQLのデータを失わないための表技
サーバーが完膚なきまでに死んでもMySQLのデータを失わないための表技サーバーが完膚なきまでに死んでもMySQLのデータを失わないための表技
サーバーが完膚なきまでに死んでもMySQLのデータを失わないための表技yoku0825
 
MySQLレプリケーションあれやこれや
MySQLレプリケーションあれやこれやMySQLレプリケーションあれやこれや
MySQLレプリケーションあれやこれやyoku0825
 
MySQL 8.0で憶えておいてほしいこと
MySQL 8.0で憶えておいてほしいことMySQL 8.0で憶えておいてほしいこと
MySQL 8.0で憶えておいてほしいことyoku0825
 
片手間MySQLチューニング戦略
片手間MySQLチューニング戦略片手間MySQLチューニング戦略
片手間MySQLチューニング戦略yoku0825
 
MySQLを割と一人で300台管理する技術
MySQLを割と一人で300台管理する技術MySQLを割と一人で300台管理する技術
MySQLを割と一人で300台管理する技術yoku0825
 
MySQLステータスモニタリング
MySQLステータスモニタリングMySQLステータスモニタリング
MySQLステータスモニタリングyoku0825
 
わかった気になるMySQL
わかった気になるMySQLわかった気になるMySQL
わかった気になるMySQLyoku0825
 
わたしを支える技術
わたしを支える技術わたしを支える技術
わたしを支える技術yoku0825
 
MySQL 5.7の次のMySQL 8.0はどんなものになるだろう
MySQL 5.7の次のMySQL 8.0はどんなものになるだろうMySQL 5.7の次のMySQL 8.0はどんなものになるだろう
MySQL 5.7の次のMySQL 8.0はどんなものになるだろうyoku0825
 
Dockerイメージで誰でも気軽にMroonga体験
Dockerイメージで誰でも気軽にMroonga体験Dockerイメージで誰でも気軽にMroonga体験
Dockerイメージで誰でも気軽にMroonga体験yoku0825
 
MySQLerの7つ道具 plus
MySQLerの7つ道具 plusMySQLerの7つ道具 plus
MySQLerの7つ道具 plusyoku0825
 
MySQL 5.7の次のMySQLは
MySQL 5.7の次のMySQLはMySQL 5.7の次のMySQLは
MySQL 5.7の次のMySQLはyoku0825
 
MySQLerの7つ道具
MySQLerの7つ道具MySQLerの7つ道具
MySQLerの7つ道具yoku0825
 
MHAの次を目指す mikasafabric for MySQL
MHAの次を目指す mikasafabric for MySQLMHAの次を目指す mikasafabric for MySQL
MHAの次を目指す mikasafabric for MySQLyoku0825
 
5.7の次のMySQL
5.7の次のMySQL5.7の次のMySQL
5.7の次のMySQLyoku0825
 
mikasafabric for MySQL
mikasafabric for MySQLmikasafabric for MySQL
mikasafabric for MySQLyoku0825
 
とあるイルカの近況報告
とあるイルカの近況報告とあるイルカの近況報告
とあるイルカの近況報告yoku0825
 
MySQL Fabricでぼっこぼこにされたはなし
MySQL FabricでぼっこぼこにされたはなしMySQL Fabricでぼっこぼこにされたはなし
MySQL Fabricでぼっこぼこにされたはなしyoku0825
 
MySQLと正規形のはなし
MySQLと正規形のはなしMySQLと正規形のはなし
MySQLと正規形のはなしyoku0825
 

More from yoku0825 (20)

逝くぞ最新版、罠の貯蔵は十分か
逝くぞ最新版、罠の貯蔵は十分か逝くぞ最新版、罠の貯蔵は十分か
逝くぞ最新版、罠の貯蔵は十分か
 
サーバーが完膚なきまでに死んでもMySQLのデータを失わないための表技
サーバーが完膚なきまでに死んでもMySQLのデータを失わないための表技サーバーが完膚なきまでに死んでもMySQLのデータを失わないための表技
サーバーが完膚なきまでに死んでもMySQLのデータを失わないための表技
 
MySQLレプリケーションあれやこれや
MySQLレプリケーションあれやこれやMySQLレプリケーションあれやこれや
MySQLレプリケーションあれやこれや
 
MySQL 8.0で憶えておいてほしいこと
MySQL 8.0で憶えておいてほしいことMySQL 8.0で憶えておいてほしいこと
MySQL 8.0で憶えておいてほしいこと
 
片手間MySQLチューニング戦略
片手間MySQLチューニング戦略片手間MySQLチューニング戦略
片手間MySQLチューニング戦略
 
MySQLを割と一人で300台管理する技術
MySQLを割と一人で300台管理する技術MySQLを割と一人で300台管理する技術
MySQLを割と一人で300台管理する技術
 
MySQLステータスモニタリング
MySQLステータスモニタリングMySQLステータスモニタリング
MySQLステータスモニタリング
 
わかった気になるMySQL
わかった気になるMySQLわかった気になるMySQL
わかった気になるMySQL
 
わたしを支える技術
わたしを支える技術わたしを支える技術
わたしを支える技術
 
MySQL 5.7の次のMySQL 8.0はどんなものになるだろう
MySQL 5.7の次のMySQL 8.0はどんなものになるだろうMySQL 5.7の次のMySQL 8.0はどんなものになるだろう
MySQL 5.7の次のMySQL 8.0はどんなものになるだろう
 
Dockerイメージで誰でも気軽にMroonga体験
Dockerイメージで誰でも気軽にMroonga体験Dockerイメージで誰でも気軽にMroonga体験
Dockerイメージで誰でも気軽にMroonga体験
 
MySQLerの7つ道具 plus
MySQLerの7つ道具 plusMySQLerの7つ道具 plus
MySQLerの7つ道具 plus
 
MySQL 5.7の次のMySQLは
MySQL 5.7の次のMySQLはMySQL 5.7の次のMySQLは
MySQL 5.7の次のMySQLは
 
MySQLerの7つ道具
MySQLerの7つ道具MySQLerの7つ道具
MySQLerの7つ道具
 
MHAの次を目指す mikasafabric for MySQL
MHAの次を目指す mikasafabric for MySQLMHAの次を目指す mikasafabric for MySQL
MHAの次を目指す mikasafabric for MySQL
 
5.7の次のMySQL
5.7の次のMySQL5.7の次のMySQL
5.7の次のMySQL
 
mikasafabric for MySQL
mikasafabric for MySQLmikasafabric for MySQL
mikasafabric for MySQL
 
とあるイルカの近況報告
とあるイルカの近況報告とあるイルカの近況報告
とあるイルカの近況報告
 
MySQL Fabricでぼっこぼこにされたはなし
MySQL FabricでぼっこぼこにされたはなしMySQL Fabricでぼっこぼこにされたはなし
MySQL Fabricでぼっこぼこにされたはなし
 
MySQLと正規形のはなし
MySQLと正規形のはなしMySQLと正規形のはなし
MySQLと正規形のはなし
 

Recently uploaded

IFIP IP3での資格制度を対象とする国際認定(IPSJ86全国大会シンポジウム)
IFIP IP3での資格制度を対象とする国際認定(IPSJ86全国大会シンポジウム)IFIP IP3での資格制度を対象とする国際認定(IPSJ86全国大会シンポジウム)
IFIP IP3での資格制度を対象とする国際認定(IPSJ86全国大会シンポジウム)ssuser539845
 
2024 02 Nihon-Tanken ~Towards a More Inclusive Japan~
2024 02 Nihon-Tanken ~Towards a More Inclusive Japan~2024 02 Nihon-Tanken ~Towards a More Inclusive Japan~
2024 02 Nihon-Tanken ~Towards a More Inclusive Japan~arts yokohama
 
「今からでも間に合う」GPTsによる 活用LT会 - 人とAIが協調するHumani-in-the-Loopへ
「今からでも間に合う」GPTsによる 活用LT会 - 人とAIが協調するHumani-in-the-Loopへ「今からでも間に合う」GPTsによる 活用LT会 - 人とAIが協調するHumani-in-the-Loopへ
「今からでも間に合う」GPTsによる 活用LT会 - 人とAIが協調するHumani-in-the-LoopへTetsuya Nihonmatsu
 
TaketoFujikawa_台本中の動作表現に基づくアニメーション原画システムの提案_SIGEC71.pdf
TaketoFujikawa_台本中の動作表現に基づくアニメーション原画システムの提案_SIGEC71.pdfTaketoFujikawa_台本中の動作表現に基づくアニメーション原画システムの提案_SIGEC71.pdf
TaketoFujikawa_台本中の動作表現に基づくアニメーション原画システムの提案_SIGEC71.pdfMatsushita Laboratory
 
持続可能なDrupal Meetupのコツ - Drupal Meetup Tokyoの知見
持続可能なDrupal Meetupのコツ - Drupal Meetup Tokyoの知見持続可能なDrupal Meetupのコツ - Drupal Meetup Tokyoの知見
持続可能なDrupal Meetupのコツ - Drupal Meetup Tokyoの知見Shumpei Kishi
 
20240326_IoTLT_vol109_kitazaki_v1___.pdf
20240326_IoTLT_vol109_kitazaki_v1___.pdf20240326_IoTLT_vol109_kitazaki_v1___.pdf
20240326_IoTLT_vol109_kitazaki_v1___.pdfAyachika Kitazaki
 
2024 01 Virtual_Counselor
2024 01 Virtual_Counselor 2024 01 Virtual_Counselor
2024 01 Virtual_Counselor arts yokohama
 
情報処理学会86回全国大会_Generic OAMをDeep Learning技術によって実現するための課題と解決方法
情報処理学会86回全国大会_Generic OAMをDeep Learning技術によって実現するための課題と解決方法情報処理学会86回全国大会_Generic OAMをDeep Learning技術によって実現するための課題と解決方法
情報処理学会86回全国大会_Generic OAMをDeep Learning技術によって実現するための課題と解決方法ssuser370dd7
 

Recently uploaded (11)

What is the world where you can make your own semiconductors?
What is the world where you can make your own semiconductors?What is the world where you can make your own semiconductors?
What is the world where you can make your own semiconductors?
 
IFIP IP3での資格制度を対象とする国際認定(IPSJ86全国大会シンポジウム)
IFIP IP3での資格制度を対象とする国際認定(IPSJ86全国大会シンポジウム)IFIP IP3での資格制度を対象とする国際認定(IPSJ86全国大会シンポジウム)
IFIP IP3での資格制度を対象とする国際認定(IPSJ86全国大会シンポジウム)
 
2024 02 Nihon-Tanken ~Towards a More Inclusive Japan~
2024 02 Nihon-Tanken ~Towards a More Inclusive Japan~2024 02 Nihon-Tanken ~Towards a More Inclusive Japan~
2024 02 Nihon-Tanken ~Towards a More Inclusive Japan~
 
2024 04 minnanoito
2024 04 minnanoito2024 04 minnanoito
2024 04 minnanoito
 
「今からでも間に合う」GPTsによる 活用LT会 - 人とAIが協調するHumani-in-the-Loopへ
「今からでも間に合う」GPTsによる 活用LT会 - 人とAIが協調するHumani-in-the-Loopへ「今からでも間に合う」GPTsによる 活用LT会 - 人とAIが協調するHumani-in-the-Loopへ
「今からでも間に合う」GPTsによる 活用LT会 - 人とAIが協調するHumani-in-the-Loopへ
 
2024 03 CTEA
2024 03 CTEA2024 03 CTEA
2024 03 CTEA
 
TaketoFujikawa_台本中の動作表現に基づくアニメーション原画システムの提案_SIGEC71.pdf
TaketoFujikawa_台本中の動作表現に基づくアニメーション原画システムの提案_SIGEC71.pdfTaketoFujikawa_台本中の動作表現に基づくアニメーション原画システムの提案_SIGEC71.pdf
TaketoFujikawa_台本中の動作表現に基づくアニメーション原画システムの提案_SIGEC71.pdf
 
持続可能なDrupal Meetupのコツ - Drupal Meetup Tokyoの知見
持続可能なDrupal Meetupのコツ - Drupal Meetup Tokyoの知見持続可能なDrupal Meetupのコツ - Drupal Meetup Tokyoの知見
持続可能なDrupal Meetupのコツ - Drupal Meetup Tokyoの知見
 
20240326_IoTLT_vol109_kitazaki_v1___.pdf
20240326_IoTLT_vol109_kitazaki_v1___.pdf20240326_IoTLT_vol109_kitazaki_v1___.pdf
20240326_IoTLT_vol109_kitazaki_v1___.pdf
 
2024 01 Virtual_Counselor
2024 01 Virtual_Counselor 2024 01 Virtual_Counselor
2024 01 Virtual_Counselor
 
情報処理学会86回全国大会_Generic OAMをDeep Learning技術によって実現するための課題と解決方法
情報処理学会86回全国大会_Generic OAMをDeep Learning技術によって実現するための課題と解決方法情報処理学会86回全国大会_Generic OAMをDeep Learning技術によって実現するための課題と解決方法
情報処理学会86回全国大会_Generic OAMをDeep Learning技術によって実現するための課題と解決方法
 

Where狙いのキー、order by狙いのキー

  • 1. WHERE狙いのキー、 ORDER BY狙いのキー 2014/08/28 yoku0825 YAPC::Asia Tokyo 2014
  • 5. Extra: Using where; Using filesort
  • 8. という話を コードで例えて お話ししたいと思います Perlがへたっぴなのは 大目にみてくだしあ。。
  • 11. I'm yoku0825 ● とある企業のDBA ● オラクれない ● ポスグれない ● マイエスキューエる ● 家に帰ると ● 嫁の夫 ● せがれの父 ● 馬鹿だからかわいいわけじゃなくて、かわいい イルカがたまたまバカだった
  • 12. はじめに ● サンプルデータは MySQLのサンプルデータ ベース(worldデータベース)からインデック スを全て取っ払ったものです ● http://dev.mysql.com/doc/index-other.html ● コードはgithubに上げてあります ● https://github.com/yoku0825/yapc_2014 ● すごく…ウンコードです…
  • 13. はじめに ● 原則、MySQLは1つのテーブルにつき同時に1 つのインデックスしか使いません ● Index mergeとかあるけどアレは例外だし狙って やっても速くなる訳でもないので除外 ● 自己結合ジョインは別 ● この資料はBKAやICP, MRRとかには対応して いません ● リードバッファやジョインバッファも非対応 ● 雰囲気だけなんとなく感じてください
  • 14. As A Codeとか言いながら 最初にトランプの話をします。
  • 17. これがテーブルスキャン SELECT * FROM cards WHERE mark= 'spade' AND number= 'A';
  • 19. マーク上から club 4,13,25,28,31,34,35,36,37,39,43,46, 47,54,62,66,71,74,77,84,85,86,89,9 6,100 diamond 1,5,15,23,24,26,32,33,41,45,48,49,5 1,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,9 7,99
  • 21. マーク数字上から 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 ..
  • 22. これがWHERE句を全て インデックスで解決したパターン SELECT * FROM cards WHERE mark= 'spade' AND number= 'A';
  • 24. マーク数字上から club 1 54,77 club 3 74,84 .. heart A 29,70 .. heart J 53,58 heart Q 50 heart K 17,22,69 ..
  • 25. マーク数字上から club 1 54,77 club 3 74,84 .. heart A 29,70 .. heart J 53,58 heart Q 50 heart K 17,22,69 ..
  • 28. テーブルデータ $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', .. }, .. hashrefを要素にした arrayrefで表現 display_table_structure.pl
  • 29. テーブルデータ $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
  • 30. テーブルスキャン 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件取り出す
  • 31. テーブルスキャン 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("%st%st%dn", $row->{name}, $row->{continent}, $row->{population}); if (++$count >= 5) {last;} } scan_where_scan_orderby.pl
  • 32. テーブルスキャン 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("%st%st%dn", $row->{name}, $row->{continent}, $row->{population}); if (++$count >= 5) {last;} } 1行ずつフェッチして scan_where_scan_orderby.pl
  • 33. テーブルスキャン 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("%st%st%dn", $row->{name}, $row->{continent}, $row->{population}); if (++$count >= 5) {last;} } WHERE句で評価 scan_where_scan_orderby.pl
  • 34. テーブルスキャン 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("%st%st%dn", $row->{name}, $row->{continent}, $row->{population}); if (++$count >= 5) {last;} } マッチしたら ソートバッファに詰める scan_where_scan_orderby.pl
  • 35. テーブルスキャン 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("%st%st%dn", $row->{name}, $row->{continent}, $row->{population}); if (++$count >= 5) {last;} } ソートバッファの中身を クイックソート scan_where_scan_orderby.pl
  • 36. テーブルスキャン 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("%st%st%dn", $row->{name}, $row->{continent}, $row->{population}); if (++$count >= 5) {last;} } 先頭の5件を出力したら そこでループを抜ける scan_where_scan_orderby.pl
  • 37. テーブルスキャン $ ./scan_where_scan_orderby.pl Maldives Asia 286000 Brunei Asia 328000 Macao Asia 473000 Qatar Asia 599000 Bahrain Asia 617000 Total rows evaluted are 239, sorted are 51. scan_where_scan_orderby.pl 5行の出力に対して、 239回のWHERE評価と 51行のファイルソート
  • 40. インデックス $VAR1 = [ { 'Africa' => [ 2, 17, 19, .., ] }, { 'Antarctica' => [ 11, 12, 34, .., ] }, { 'Asia' => [ 1, 7, 9, .., カラムの値をkey, 対応する行のプライマリーキーの arrayrefをvalueにした hashrefのarrayref (ソート済みを表現したかった) display_single_column_index_structure.pl
  • 41. インデックス $VAR1 = [ { 'Africa' => [ 2, 17, 19, .., ] }, { 'Antarctica' => [ 11, 12, 34, .., ] }, { 'Asia' => [ 1, 7, 9, .., Continent = 'Africa'な 行IDを取り出すには $index->[0]->{Africa} (arrayrefとして取り出す) display_single_column_index_structure.pl
  • 42. インデックス $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]]
  • 44. インデックス $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}に押し込めた こっちはarrayrefのarrayrefに display_single_column_index_structure.pl
  • 45. インデックス $index_num= $index->{map}->{Africa} $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 $rownum_array= $index->{index}->[$index_num} foreach my $rownum(@$rownum_array) { my $row= $table->[$rownum]; .. }
  • 47. 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件取り出す
  • 48. 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("%st%st%dn", $row->{name}, $row->{continent}, $row->{population}); if (++$count >= 5) {last;} } indexed_where_scan_orderby.pl
  • 49. 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("%st%st%dn", $row->{name}, $row->{continent}, $row->{population}); if (++$count >= 5) {last;} } WHERE句のレンジを インデックスから取り出す indexed_where_scan_orderby.pl
  • 50. 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("%st%st%dn", $row->{name}, $row->{continent}, $row->{population}); if (++$count >= 5) {last;} } 取り出した行を全部 ソートバッファに詰めて indexed_where_scan_orderby.pl
  • 51. 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("%st%st%dn", $row->{name}, $row->{continent}, $row->{population}); if (++$count >= 5) {last;} } クイックソートして indexed_where_scan_orderby.pl
  • 52. 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("%st%st%dn", $row->{name}, $row->{continent}, $row->{population}); if (++$count >= 5) {last;} } 先頭の5件を出力したら ループを抜ける indexed_where_scan_orderby.pl
  • 53. WHERE狙いのキー $ ./indexed_where_scan_orderby.pl Maldives Asia 286000 Brunei Asia 328000 Macao Asia 473000 Qatar Asia 599000 Bahrain Asia 617000 Total rows evaluted are 1, sorted are 51. WHEREの評価は1回で済むけど indexed_where_scan_orderby.pl 51行をファイルソート
  • 55. インデックス $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], .. ]
  • 56. 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件データが揃ったらループから抜ける
  • 57. 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("%st%st%dn", $row->{name}, $row->{continent}, $row->{population}); if (++$count >= 5) {last LOOP;} } } } scan_where_indexed_orderby.pl
  • 58. 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("%st%st%dn", $row->{name}, $row->{continent}, $row->{population}); if (++$count >= 5) {last LOOP;} } } } インデックスに沿って 行を取り出して scan_where_indexed_orderby.pl
  • 59. 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("%st%st%dn", $row->{name}, $row->{continent}, $row->{population}); if (++$count >= 5) {last LOOP;} } } } WHERE句にマッチするか 判定して scan_where_indexed_orderby.pl
  • 60. 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("%st%st%dn", $row->{name}, $row->{continent}, $row->{population}); if (++$count >= 5) {last LOOP;} } } } 先頭の5件を出力したら そこでループを抜ける scan_where_indexed_orderby.pl
  • 61. ORDER BY狙いのキー $ ./scan_where_indexed_orderby.pl Maldives Asia 286000 Brunei Asia 328000 Macao Asia 473000 Qatar Asia 599000 Bahrain Asia 617000 Total rows evaluted are 79, sorted are 0. ソートのオーバーヘッドはないものの、 scan_where_indexed_orderby.pl 79行をWHERE句評価
  • 63. 複合インデックス $VAR1 = [ { 'Africa' => [ { '0' => [ 100 ] }, { '6000' => [ 188 ] }, .. ] }, { 'Antarctica' => [ { '0' => [ 11, 12, 34, 93, 187 ] } ] }, .. display_double_column_index_structure.pl
  • 64. 複合インデックス $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
  • 65. 複合インデックス $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
  • 66. 複合インデックス $VAR1 = { 'map' => { 'Oceania' => { '83000' => 12, '3862000' => 22, '235000' => 19, 'self' => 5, .. }, 'North America' => { '154000' => 14, '21000' => 4, .. }, } }, 'index' => [ [ [ 100 ], [ 188 ], .. マジックナンバーよけをしたら かなりカオス selfを予約語にしてるのが ものすごくイケてない display_double_column_index_structure.pl
  • 67. 複合インデックス $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 ..
  • 68. 複合インデックス $VAR1 = { 'map' => { 'Oceania' => { '83000' => 12, '3862000' => 22, '235000' => 19, 'self' => 5, .. }, 'North America' => { my $index_nums_hash= $index->{map}->{Oceania}->{self}; '154000' => 14, '21000' => 4, .. }, } }, 'index' => [ [ [ 100 ], [ 188 ], .. display_double_column_index_structure.pl foreach my $index_num (values(%$index_nums_hash)) .. Foreach ..
  • 70. 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件データが揃ったらループから抜ける
  • 71. 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("%st%st%dn", $row->{name}, $row->{continent}, $row->{population}); if (++$count >= 5) {last LOOP;} } } indexed_where_indexed_orderby.pl
  • 72. 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("%st%st%dn", $row->{name}, $row->{continent}, $row->{population}); if (++$count >= 5) {last LOOP;} } } WHERE句のレンジを インデックスから取り出す indexed_where_indexed_orderby.pl
  • 73. 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("%st%st%dn", $row->{name}, $row->{continent}, $row->{population}); if (++$count >= 5) {last LOOP;} } } インデックスに沿って 行を取り出して indexed_where_indexed_orderby.pl
  • 74. 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("%st%st%dn", $row->{name}, $row->{continent}, $row->{population}); if (++$count >= 5) {last LOOP;} } } 先頭の5件を出力したら そこでループを抜ける indexed_where_indexed_orderby.pl
  • 78. スキャンジョイン 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件取り出す
  • 79. スキャンジョイン 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
  • 80. スキャンジョイン 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
  • 81. スキャンジョイン 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
  • 82. スキャンジョイン 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
  • 83. スキャンジョイン $ ./scan_join_scan_where_scan_orderby.pl United Arab Emirates Hindi 2441000 0.000000 Bahrain English 617000 0.000000 Japan Ainu 126714000 0.000000 Kuwait English 1972000 0.000000 Lebanon French 3282000 0.000000 Total rows evaluted are 235176, sorted are 239. 評価する行の数が倍倍ゲェム scan_join_scan_where_scan_orderby.pl
  • 85. 直感の赴くまま SELECT Name, Language, Population, Percentage FROM Country INNER JOIN CountryLanguage ON Country.Code= CountryLanguage.CountryCode WHERE 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狙いのキーになる。 わかりますん。
  • 86. 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件取り出す
  • 87. 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
  • 88. 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
  • 89. 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
  • 90. 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
  • 91. JOIN de WHERE狙いのキー $ ./indexed_join_indexed_where_scan_orderby.pl United Arab Emirates Hindi 2441000 0.000000 Bahrain English 617000 0.000000 Japan Ainu 126714000 0.000000 Kuwait English 1972000 0.000000 Lebanon French 3282000 0.000000 Total rows evaluted are 52, sorted are 239. スキャンジョインよりよっぽどいいけど そんなに効率が良いわけではなさそう indexed_join_indexed_where_scan_orderby.pl
  • 93. 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.Co untry.Code | 2 | NULL | +----+-------------+-----------------+------+------------------------------+------------------------------+---------+--------- -----------+------+--------------------------------------------------------+ 2 rows in set (0.00 sec) * 外部表と内部表が入れ替わってる * ORDER BYをインデックスで解決するには、 そのカラムが外部表にあることが必要
  • 95. 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("* %st%dt%s => %st%fn", $country_row->{name}, $country_row->{population}, $country_row->{code}, $language_row->{language}, $language_row->{percentage}); } } } verbose_indexed_join_indexed_where_scan_orderby.pl
  • 96. 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
  • 97. 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 こっちがCountry こっちがCountryLanguage
  • 99. ORDER BY狙いのキーを使うには どちらが外部表で どう結合されるかをイメージ
  • 100. 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 .. こっちがCountry verbose_indexed_join_indexed_where_indexed_orderby.pl こっちがCountryLanguage
  • 101. 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 ほしいから こっち向きに 結合してほしい
  • 102. 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行マッチしたら終了
  • 103. 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("%st%st%dt%fn", $country_row->{name}, $language_row->{language}, $country_row->{population}, $language_row->{percentage}); if (++$count >= 5) {last LOOP;} } } } indexed_join_indexed_where_indexed_orderby.pl
  • 104. 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("%st%st%dt%fn", $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
  • 105. 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("%st%st%dt%fn", $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
  • 106. WHERE狙いのキー ORDER BY狙いのキー ● もちろん両方狙えるインデックスを作っていく のが最良。 ● ただし必ずしも一番良いキーでWHEREとORDER BY を両方狙い打ちできるとは限らない。 ● どっちがどういう動作になるかがイメージできれ ば、打ち分けるのはそう難しくない ● ちゃんと狙ったクエリーとそうでないクエリーは 100倍くらい性能が違うこともザラ。
  • 107. まとめ ● WHERE句で十分絞り込める場合はWHERE狙い ● たとえばWHERE Continent = 'Moo'(マッチ0件) だったらWHERE狙い。ORDER BY狙いだと5件揃わ ないのでテーブルをまるまるスキャンする。 ● WHERE句がほとんど機能しないような場合は ORDER BY狙い ● 同じカラム同じカーディナリティーでも、値によっ て本当は違う。 ● このセッションを聞いている人をWHERE gender = 'male'ならORDER BYを狙った方がいいだろう し、WHERE gender = 'female'ならWHEREを狙った 方がいい。
  • 108. 愚痴 ● 本当はそのあたりの按配をオプティマイザー氏 が上手くやってくれると嬉しい。 ● Oracle DBは上手くやってくれるらしい(って聞い た。ホントかどうかは知らない) ● MySQLのオプティマイザーが残念なのはInnoDBの 統計情報のせいもある。 – InnoDBの統計情報はサンプリング(デフォルトでは1イ ンデックスあたり8ページ) ● 8ページ= 128kB、テーブルサイズがどれだけあろうとも。 – 俺たちが「MySQLのオプティマイザー氏はバカ」なんて 言ってられるのは、「そのカラムの偏りが(設計上や サービス上)どうなりそうか」という高精度な予測を 持っているから。