SlideShare a Scribd company logo
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

ヤフー社内でやってるMySQLチューニングセミナー大公開
ヤフー社内でやってるMySQLチューニングセミナー大公開ヤフー社内でやってるMySQLチューニングセミナー大公開
ヤフー社内でやってるMySQLチューニングセミナー大公開
Yahoo!デベロッパーネットワーク
 
なぜ、いま リレーショナルモデルなのか(理論から学ぶデータベース実践入門読書会スペシャル)
なぜ、いま リレーショナルモデルなのか(理論から学ぶデータベース実践入門読書会スペシャル)なぜ、いま リレーショナルモデルなのか(理論から学ぶデータベース実践入門読書会スペシャル)
なぜ、いま リレーショナルモデルなのか(理論から学ぶデータベース実践入門読書会スペシャル)
Mikiya Okuno
 
なかったらINSERTしたいし、あるならロック取りたいやん?
なかったらINSERTしたいし、あるならロック取りたいやん?なかったらINSERTしたいし、あるならロック取りたいやん?
なかったらINSERTしたいし、あるならロック取りたいやん?
ichirin2501
 
MySQLレプリケーションあれやこれや
MySQLレプリケーションあれやこれやMySQLレプリケーションあれやこれや
MySQLレプリケーションあれやこれや
yoku0825
 
SQL大量発行処理をいかにして高速化するか
SQL大量発行処理をいかにして高速化するかSQL大量発行処理をいかにして高速化するか
SQL大量発行処理をいかにして高速化するか
Shogo Wakayama
 
マイクロにしすぎた結果がこれだよ!
マイクロにしすぎた結果がこれだよ!マイクロにしすぎた結果がこれだよ!
マイクロにしすぎた結果がこれだよ!
mosa siru
 
MySQL 5.7にやられないためにおぼえておいてほしいこと
MySQL 5.7にやられないためにおぼえておいてほしいことMySQL 5.7にやられないためにおぼえておいてほしいこと
MySQL 5.7にやられないためにおぼえておいてほしいこと
yoku0825
 
マルチテナントのアプリケーション実装〜実践編〜
マルチテナントのアプリケーション実装〜実践編〜マルチテナントのアプリケーション実装〜実践編〜
マルチテナントのアプリケーション実装〜実践編〜
Yoshiki Nakagawa
 
イミュータブルデータモデルの極意
イミュータブルデータモデルの極意イミュータブルデータモデルの極意
イミュータブルデータモデルの極意
Yoshitaka Kawashima
 
MySQLアンチパターン
MySQLアンチパターンMySQLアンチパターン
MySQLアンチパターン
yoku0825
 
DDD x CQRS 更新系と参照系で異なるORMを併用して上手くいった話
DDD x CQRS   更新系と参照系で異なるORMを併用して上手くいった話DDD x CQRS   更新系と参照系で異なるORMを併用して上手くいった話
DDD x CQRS 更新系と参照系で異なるORMを併用して上手くいった話
Koichiro Matsuoka
 
Java ORマッパー選定のポイント #jsug
Java ORマッパー選定のポイント #jsugJava ORマッパー選定のポイント #jsug
Java ORマッパー選定のポイント #jsug
Masatoshi Tada
 
[Aurora事例祭り]Amazon Aurora を使いこなすためのベストプラクティス
[Aurora事例祭り]Amazon Aurora を使いこなすためのベストプラクティス[Aurora事例祭り]Amazon Aurora を使いこなすためのベストプラクティス
[Aurora事例祭り]Amazon Aurora を使いこなすためのベストプラクティス
Amazon Web Services Japan
 
ソーシャルゲームのためのデータベース設計
ソーシャルゲームのためのデータベース設計ソーシャルゲームのためのデータベース設計
ソーシャルゲームのためのデータベース設計
Yoshinori Matsunobu
 
外部キー制約に伴うロックの小話
外部キー制約に伴うロックの小話外部キー制約に伴うロックの小話
外部キー制約に伴うロックの小話
ichirin2501
 
SPAセキュリティ入門~PHP Conference Japan 2021
SPAセキュリティ入門~PHP Conference Japan 2021SPAセキュリティ入門~PHP Conference Japan 2021
SPAセキュリティ入門~PHP Conference Japan 2021
Hiroshi Tokumaru
 
PostgreSQLの行レベルセキュリティと SpringAOPでマルチテナントの ユーザー間情報漏洩を防止する (JJUG CCC 2021 Spring)
PostgreSQLの行レベルセキュリティと SpringAOPでマルチテナントの ユーザー間情報漏洩を防止する (JJUG CCC 2021 Spring)PostgreSQLの行レベルセキュリティと SpringAOPでマルチテナントの ユーザー間情報漏洩を防止する (JJUG CCC 2021 Spring)
PostgreSQLの行レベルセキュリティと SpringAOPでマルチテナントの ユーザー間情報漏洩を防止する (JJUG CCC 2021 Spring)
Koichiro Matsuoka
 
マルチテナント化で知っておきたいデータベースのこと
マルチテナント化で知っておきたいデータベースのことマルチテナント化で知っておきたいデータベースのこと
マルチテナント化で知っておきたいデータベースのこと
Amazon Web Services Japan
 
本当は恐ろしい分散システムの話
本当は恐ろしい分散システムの話本当は恐ろしい分散システムの話
本当は恐ろしい分散システムの話
Kumazaki Hiroki
 
Javaのログ出力: 道具と考え方
Javaのログ出力: 道具と考え方Javaのログ出力: 道具と考え方
Javaのログ出力: 道具と考え方
Taku Miyakawa
 

What's hot (20)

ヤフー社内でやってるMySQLチューニングセミナー大公開
ヤフー社内でやってるMySQLチューニングセミナー大公開ヤフー社内でやってるMySQLチューニングセミナー大公開
ヤフー社内でやってるMySQLチューニングセミナー大公開
 
なぜ、いま リレーショナルモデルなのか(理論から学ぶデータベース実践入門読書会スペシャル)
なぜ、いま リレーショナルモデルなのか(理論から学ぶデータベース実践入門読書会スペシャル)なぜ、いま リレーショナルモデルなのか(理論から学ぶデータベース実践入門読書会スペシャル)
なぜ、いま リレーショナルモデルなのか(理論から学ぶデータベース実践入門読書会スペシャル)
 
なかったらINSERTしたいし、あるならロック取りたいやん?
なかったらINSERTしたいし、あるならロック取りたいやん?なかったらINSERTしたいし、あるならロック取りたいやん?
なかったらINSERTしたいし、あるならロック取りたいやん?
 
MySQLレプリケーションあれやこれや
MySQLレプリケーションあれやこれやMySQLレプリケーションあれやこれや
MySQLレプリケーションあれやこれや
 
SQL大量発行処理をいかにして高速化するか
SQL大量発行処理をいかにして高速化するかSQL大量発行処理をいかにして高速化するか
SQL大量発行処理をいかにして高速化するか
 
マイクロにしすぎた結果がこれだよ!
マイクロにしすぎた結果がこれだよ!マイクロにしすぎた結果がこれだよ!
マイクロにしすぎた結果がこれだよ!
 
MySQL 5.7にやられないためにおぼえておいてほしいこと
MySQL 5.7にやられないためにおぼえておいてほしいことMySQL 5.7にやられないためにおぼえておいてほしいこと
MySQL 5.7にやられないためにおぼえておいてほしいこと
 
マルチテナントのアプリケーション実装〜実践編〜
マルチテナントのアプリケーション実装〜実践編〜マルチテナントのアプリケーション実装〜実践編〜
マルチテナントのアプリケーション実装〜実践編〜
 
イミュータブルデータモデルの極意
イミュータブルデータモデルの極意イミュータブルデータモデルの極意
イミュータブルデータモデルの極意
 
MySQLアンチパターン
MySQLアンチパターンMySQLアンチパターン
MySQLアンチパターン
 
DDD x CQRS 更新系と参照系で異なるORMを併用して上手くいった話
DDD x CQRS   更新系と参照系で異なるORMを併用して上手くいった話DDD x CQRS   更新系と参照系で異なるORMを併用して上手くいった話
DDD x CQRS 更新系と参照系で異なるORMを併用して上手くいった話
 
Java ORマッパー選定のポイント #jsug
Java ORマッパー選定のポイント #jsugJava ORマッパー選定のポイント #jsug
Java ORマッパー選定のポイント #jsug
 
[Aurora事例祭り]Amazon Aurora を使いこなすためのベストプラクティス
[Aurora事例祭り]Amazon Aurora を使いこなすためのベストプラクティス[Aurora事例祭り]Amazon Aurora を使いこなすためのベストプラクティス
[Aurora事例祭り]Amazon Aurora を使いこなすためのベストプラクティス
 
ソーシャルゲームのためのデータベース設計
ソーシャルゲームのためのデータベース設計ソーシャルゲームのためのデータベース設計
ソーシャルゲームのためのデータベース設計
 
外部キー制約に伴うロックの小話
外部キー制約に伴うロックの小話外部キー制約に伴うロックの小話
外部キー制約に伴うロックの小話
 
SPAセキュリティ入門~PHP Conference Japan 2021
SPAセキュリティ入門~PHP Conference Japan 2021SPAセキュリティ入門~PHP Conference Japan 2021
SPAセキュリティ入門~PHP Conference Japan 2021
 
PostgreSQLの行レベルセキュリティと SpringAOPでマルチテナントの ユーザー間情報漏洩を防止する (JJUG CCC 2021 Spring)
PostgreSQLの行レベルセキュリティと SpringAOPでマルチテナントの ユーザー間情報漏洩を防止する (JJUG CCC 2021 Spring)PostgreSQLの行レベルセキュリティと SpringAOPでマルチテナントの ユーザー間情報漏洩を防止する (JJUG CCC 2021 Spring)
PostgreSQLの行レベルセキュリティと SpringAOPでマルチテナントの ユーザー間情報漏洩を防止する (JJUG CCC 2021 Spring)
 
マルチテナント化で知っておきたいデータベースのこと
マルチテナント化で知っておきたいデータベースのことマルチテナント化で知っておきたいデータベースのこと
マルチテナント化で知っておきたいデータベースのこと
 
本当は恐ろしい分散システムの話
本当は恐ろしい分散システムの話本当は恐ろしい分散システムの話
本当は恐ろしい分散システムの話
 
Javaのログ出力: 道具と考え方
Javaのログ出力: 道具と考え方Javaのログ出力: 道具と考え方
Javaのログ出力: 道具と考え方
 

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 @studio3104
Satoshi Suzuki
 
本当は怖いPHP
本当は怖いPHP本当は怖いPHP
本当は怖いPHP
Takuya Sato
 
詳説ぺちぺち
詳説ぺちぺち詳説ぺちぺち
詳説ぺちぺち
do_aki
 
F#入門 ~関数プログラミングとは何か~
F#入門 ~関数プログラミングとは何か~F#入門 ~関数プログラミングとは何か~
F#入門 ~関数プログラミングとは何か~
Nobuhisa Koizumi
 
Perl for visualization
Perl for visualizationPerl for visualization
Perl for visualization
Daichi Morifuji
 
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 glance
Yuta 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 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
わかった気になるMySQL
yoku0825
 
わたしを支える技術
わたしを支える技術わたしを支える技術
わたしを支える技術
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つ道具 plus
yoku0825
 
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 MySQL
yoku0825
 
5.7の次のMySQL
5.7の次のMySQL5.7の次のMySQL
5.7の次のMySQL
yoku0825
 
mikasafabric for MySQL
mikasafabric for MySQLmikasafabric for MySQL
mikasafabric for MySQL
yoku0825
 
とあるイルカの近況報告
とあるイルカの近況報告とあるイルカの近況報告
とあるイルカの近況報告
yoku0825
 
MySQL Fabricでぼっこぼこにされたはなし
MySQL FabricでぼっこぼこにされたはなしMySQL Fabricでぼっこぼこにされたはなし
MySQL Fabricでぼっこぼこにされたはなし
yoku0825
 
MySQLと正規形のはなし
MySQLと正規形のはなしMySQLと正規形のはなし
MySQLと正規形のはなし
yoku0825
 
MySQLおじさんの逆襲
MySQLおじさんの逆襲MySQLおじさんの逆襲
MySQLおじさんの逆襲
yoku0825
 

More from yoku0825 (20)

逝くぞ最新版、罠の貯蔵は十分か
逝くぞ最新版、罠の貯蔵は十分か逝くぞ最新版、罠の貯蔵は十分か
逝くぞ最新版、罠の貯蔵は十分か
 
サーバーが完膚なきまでに死んでも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と正規形のはなし
 
MySQLおじさんの逆襲
MySQLおじさんの逆襲MySQLおじさんの逆襲
MySQLおじさんの逆襲
 

Recently uploaded

Matsuo-Iwasawa Lab. Research unit Introduction
Matsuo-Iwasawa Lab. Research unit IntroductionMatsuo-Iwasawa Lab. Research unit Introduction
Matsuo-Iwasawa Lab. Research unit Introduction
Matsuo Lab
 
"ros2rapper", Hardware implimentation of ROS2 communication Protocol without ...
"ros2rapper", Hardware implimentation of ROS2 communication Protocol without ..."ros2rapper", Hardware implimentation of ROS2 communication Protocol without ...
"ros2rapper", Hardware implimentation of ROS2 communication Protocol without ...
たけおか しょうぞう
 
【AI論文解説】LLMの事前学習をvisionに適用する手法Autoregressive Image Models
【AI論文解説】LLMの事前学習をvisionに適用する手法Autoregressive Image Models【AI論文解説】LLMの事前学習をvisionに適用する手法Autoregressive Image Models
【AI論文解説】LLMの事前学習をvisionに適用する手法Autoregressive Image Models
Sony - Neural Network Libraries
 
論文紹介:Task-aligned Part-aware Panoptic Segmentation through Joint Object-Part ...
論文紹介:Task-aligned Part-aware Panoptic Segmentation through Joint Object-Part ...論文紹介:Task-aligned Part-aware Panoptic Segmentation through Joint Object-Part ...
論文紹介:Task-aligned Part-aware Panoptic Segmentation through Joint Object-Part ...
Toru Tamaki
 
Kyndryl Developer Services のご紹介 2024年7月
Kyndryl Developer Services のご紹介  2024年7月Kyndryl Developer Services のご紹介  2024年7月
Kyndryl Developer Services のご紹介 2024年7月
Takayuki Nakayama
 
論文紹介:BAM-DETR: Boundary-Aligned Moment Detection Transformer for Temporal Sen...
論文紹介:BAM-DETR: Boundary-Aligned Moment Detection Transformer for Temporal Sen...論文紹介:BAM-DETR: Boundary-Aligned Moment Detection Transformer for Temporal Sen...
論文紹介:BAM-DETR: Boundary-Aligned Moment Detection Transformer for Temporal Sen...
Toru Tamaki
 
Imitation learning for robotics 勉強会資料(20240701)
Imitation learning for robotics 勉強会資料(20240701)Imitation learning for robotics 勉強会資料(20240701)
Imitation learning for robotics 勉強会資料(20240701)
Natsutani Minoru
 
【AI論文解説】クラスタリングベースアプローチによる大規模データセット自動キュレーション
【AI論文解説】クラスタリングベースアプローチによる大規模データセット自動キュレーション【AI論文解説】クラスタリングベースアプローチによる大規模データセット自動キュレーション
【AI論文解説】クラスタリングベースアプローチによる大規模データセット自動キュレーション
Sony - Neural Network Libraries
 
Matsuo-Iwasawa Lab. | Research unit Introduction
Matsuo-Iwasawa Lab. | Research unit IntroductionMatsuo-Iwasawa Lab. | Research unit Introduction
Matsuo-Iwasawa Lab. | Research unit Introduction
Matsuo Lab
 
最速の組織を目指して全社で大規模スクラムを導入してみた話 #dxd2024 #medicalforce
最速の組織を目指して全社で大規模スクラムを導入してみた話 #dxd2024 #medicalforce最速の組織を目指して全社で大規模スクラムを導入してみた話 #dxd2024 #medicalforce
最速の組織を目指して全社で大規模スクラムを導入してみた話 #dxd2024 #medicalforce
chisatotakane
 
Matsuo-Iwasawa lab. Research Unit Introduction
Matsuo-Iwasawa lab. Research Unit IntroductionMatsuo-Iwasawa lab. Research Unit Introduction
Matsuo-Iwasawa lab. Research Unit Introduction
Matsuo Lab
 
LoRaWAN AI Image Sensorエンドデバイス AIG01カタログ
LoRaWAN AI Image Sensorエンドデバイス AIG01カタログLoRaWAN AI Image Sensorエンドデバイス AIG01カタログ
LoRaWAN AI Image Sensorエンドデバイス AIG01カタログ
CRI Japan, Inc.
 

Recently uploaded (12)

Matsuo-Iwasawa Lab. Research unit Introduction
Matsuo-Iwasawa Lab. Research unit IntroductionMatsuo-Iwasawa Lab. Research unit Introduction
Matsuo-Iwasawa Lab. Research unit Introduction
 
"ros2rapper", Hardware implimentation of ROS2 communication Protocol without ...
"ros2rapper", Hardware implimentation of ROS2 communication Protocol without ..."ros2rapper", Hardware implimentation of ROS2 communication Protocol without ...
"ros2rapper", Hardware implimentation of ROS2 communication Protocol without ...
 
【AI論文解説】LLMの事前学習をvisionに適用する手法Autoregressive Image Models
【AI論文解説】LLMの事前学習をvisionに適用する手法Autoregressive Image Models【AI論文解説】LLMの事前学習をvisionに適用する手法Autoregressive Image Models
【AI論文解説】LLMの事前学習をvisionに適用する手法Autoregressive Image Models
 
論文紹介:Task-aligned Part-aware Panoptic Segmentation through Joint Object-Part ...
論文紹介:Task-aligned Part-aware Panoptic Segmentation through Joint Object-Part ...論文紹介:Task-aligned Part-aware Panoptic Segmentation through Joint Object-Part ...
論文紹介:Task-aligned Part-aware Panoptic Segmentation through Joint Object-Part ...
 
Kyndryl Developer Services のご紹介 2024年7月
Kyndryl Developer Services のご紹介  2024年7月Kyndryl Developer Services のご紹介  2024年7月
Kyndryl Developer Services のご紹介 2024年7月
 
論文紹介:BAM-DETR: Boundary-Aligned Moment Detection Transformer for Temporal Sen...
論文紹介:BAM-DETR: Boundary-Aligned Moment Detection Transformer for Temporal Sen...論文紹介:BAM-DETR: Boundary-Aligned Moment Detection Transformer for Temporal Sen...
論文紹介:BAM-DETR: Boundary-Aligned Moment Detection Transformer for Temporal Sen...
 
Imitation learning for robotics 勉強会資料(20240701)
Imitation learning for robotics 勉強会資料(20240701)Imitation learning for robotics 勉強会資料(20240701)
Imitation learning for robotics 勉強会資料(20240701)
 
【AI論文解説】クラスタリングベースアプローチによる大規模データセット自動キュレーション
【AI論文解説】クラスタリングベースアプローチによる大規模データセット自動キュレーション【AI論文解説】クラスタリングベースアプローチによる大規模データセット自動キュレーション
【AI論文解説】クラスタリングベースアプローチによる大規模データセット自動キュレーション
 
Matsuo-Iwasawa Lab. | Research unit Introduction
Matsuo-Iwasawa Lab. | Research unit IntroductionMatsuo-Iwasawa Lab. | Research unit Introduction
Matsuo-Iwasawa Lab. | Research unit Introduction
 
最速の組織を目指して全社で大規模スクラムを導入してみた話 #dxd2024 #medicalforce
最速の組織を目指して全社で大規模スクラムを導入してみた話 #dxd2024 #medicalforce最速の組織を目指して全社で大規模スクラムを導入してみた話 #dxd2024 #medicalforce
最速の組織を目指して全社で大規模スクラムを導入してみた話 #dxd2024 #medicalforce
 
Matsuo-Iwasawa lab. Research Unit Introduction
Matsuo-Iwasawa lab. Research Unit IntroductionMatsuo-Iwasawa lab. Research Unit Introduction
Matsuo-Iwasawa lab. Research Unit Introduction
 
LoRaWAN AI Image Sensorエンドデバイス AIG01カタログ
LoRaWAN AI Image Sensorエンドデバイス AIG01カタログLoRaWAN AI Image Sensorエンドデバイス AIG01カタログ
LoRaWAN AI Image Sensorエンドデバイス AIG01カタログ
 

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のオプティマイザー氏はバカ」なんて 言ってられるのは、「そのカラムの偏りが(設計上や サービス上)どうなりそうか」という高精度な予測を 持っているから。