109
WHERE 狙いのキー、 ORDER BY 狙いのキー 2014/08/28 yoku0825 YAPC::Asia Tokyo 2014

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

  • Upload
    yoku0825

  • View
    13.350

  • Download
    1

Embed Size (px)

DESCRIPTION

2014/08/29 YAPC::Asia 2014

Citation preview

Page 1: Where狙いのキー、order by狙いのキー

WHERE狙いのキー、ORDER BY狙いのキー

2014/08/28yoku0825

YAPC::Asia Tokyo 2014

Page 2: Where狙いのキー、order by狙いのキー

みんなEXPLAINしてるー?

目XPLAINでも可

Page 3: Where狙いのキー、order by狙いのキー

type: ALL

Page 4: Where狙いのキー、order by狙いのキー

邪悪ですね

Page 5: Where狙いのキー、order by狙いのキー

Extra: Using where; Using filesort

Page 6: Where狙いのキー、order by狙いのキー

うーん…

Page 7: Where狙いのキー、order by狙いのキー

でもなんで?

クエリーが遅い時にこいつらが出るのか、こいつらが出る時にクエリーが遅くなるのか

Page 8: Where狙いのキー、order by狙いのキー

という話をコードで例えて

お話ししたいと思いますPerlがへたっぴなのは大目にみてくだしあ。。

Page 9: Where狙いのキー、order by狙いのキー

今日の意気込み

Page 10: Where狙いのキー、order by狙いのキー

Kuso-QueryAsACode

Page 11: Where狙いのキー、order by狙いのキー

I'm yoku0825

● とある企業のDBA● オラクれない● ポスグれない● マイエスキューエる

● 家に帰ると● 嫁の夫● せがれの父

● 馬鹿だからかわいいわけじゃなくて、かわいいイルカがたまたまバカだった

Page 12: Where狙いのキー、order by狙いのキー

はじめに

● サンプルデータは MySQLのサンプルデータベース(worldデータベース)からインデックスを全て取っ払ったものです● http://dev.mysql.com/doc/index-other.html

● コードはgithubに上げてあります● https://github.com/yoku0825/yapc_2014● すごく…ウンコードです…

Page 13: Where狙いのキー、order by狙いのキー

はじめに

● 原則、MySQLは 1つのテーブルにつき同時に1つのインデックスしか使いません● Index mergeとかあるけどアレは例外だし狙ってやっても速くなる訳でもないので除外

● 自己結合ジョインは別

● この資料はBKAや ICP, MRRとかには対応していません● リードバッファやジョインバッファも非対応● 雰囲気だけなんとなく感じてください

Page 14: Where狙いのキー、order by狙いのキー

As A Codeとか言いながら最初にトランプの話をします。

Page 15: Where狙いのキー、order by狙いのキー

100枚のトランプの山があります。

何組かのトランプを混ぜたもので何が何枚入ってるかはわかりません。

Page 16: Where狙いのキー、order by狙いのキー

この山の中からスペードのAを探してください

Page 17: Where狙いのキー、order by狙いのキー

これがテーブルスキャン

SELECT * FROM cardsWHERE mark= 'spade' AND number= 'A';

Page 18: Where狙いのキー、order by狙いのキー

面倒なので、チートシートを作ります

Page 19: Where狙いのキー、order by狙いのキー

マーク 上から

club 4,13,25,28,31,34,35,36,37,39,43,46,47,54,62,66,71,74,77,84,85,86,89,96,100

diamond 1,5,15,23,24,26,32,33,41,45,48,49,51,57,63,65,67,68,73,76,80,81,82,90,93,95,98

heart 2,3,7,11,12,17,22,27,29,50,52,53,56,58,64,69,70,72,78,87,92,94

spade 6,8,9,10,14,16,18,19,20,21,30,38,40,42,44,55,59,60,61,75,79,83,88,91,97,99

Page 20: Where狙いのキー、order by狙いのキー

これがWHERE句を部分的にインデックスで解決したパターン

SELECT * FROM cardsWHERE mark= 'spade' AND number= 'A';

Page 21: Where狙いのキー、order by狙いのキー

マーク 数字 上から

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

..

Page 22: Where狙いのキー、order by狙いのキー

これがWHERE句を全てインデックスで解決したパターン

SELECT * FROM cardsWHERE mark= 'spade' AND number= 'A';

Page 23: Where狙いのキー、order by狙いのキー

このチートシートを使っていいから、山の中からハートを

KからAに向かって並べておくれSELECT * FROM cardsWHERE mark= 'heart'ORDER BY number DESC;

Page 24: Where狙いのキー、order by狙いのキー

マーク 数字 上から

club 1 54,77

club 3 74,84

..

heart A 29,70

..

heart J 53,58

heart Q 50

heart K 17,22,69

..

Page 25: Where狙いのキー、order by狙いのキー

マーク 数字 上から

club 1 54,77

club 3 74,84

..

heart A 29,70

..

heart J 53,58

heart Q 50

heart K 17,22,69

..

Page 26: Where狙いのキー、order by狙いのキー

インデックスとはソート済みのデータの複製

異論はあると思う

Page 27: Where狙いのキー、order by狙いのキー

じゃあコードに行きます

Page 28: Where狙いのキー、order by狙いのキー

テーブルデータ

$VAR1 = [ { 'population' => '103000', 'region' => 'Caribbean', 'name' => 'Aruba', 'continent' => 'North America', 'code' => 'ABW', .. }, { 'population' => '22720000', 'region' => 'Southern and Central Asia', 'name' => 'Afghanistan', 'continent' => 'Asia', 'code' => 'AFG', .. }, { 'population' => '12878000', 'region' => 'Central Africa', 'name' => 'Angola', 'continent' => 'Africa', 'code' => 'AGO', .. }, ..

display_table_structure.pl

hashrefを要素にしたarrayrefで表現

Page 29: Where狙いのキー、order by狙いのキー

テーブルデータ

$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

Page 30: Where狙いのキー、order by狙いのキー

テーブルスキャン

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件取り出す

Page 31: Where狙いのキー、order by狙いのキー

テーブルスキャン

for (my $rownum= 0; $rownum < scalar(@$country_table); $rownum++){ my $row= $country_table->[$rownum];

$evaluted++; if ($row->{continent} eq "Asia") { $sorted++; push(@{$sort_buffer->{$row->{population}}}, $rownum); }}

my $sorted_buffer= filesort_single_column($sort_buffer);

foreach my $rownum (@$sorted_buffer){ my $row= $country_table->[$rownum];

printf("%s\t%s\t%d\n", $row->{name}, $row->{continent}, $row->{population}); if (++$count >= 5) {last;}}

scan_where_scan_orderby.pl

Page 32: Where狙いのキー、order by狙いのキー

テーブルスキャン

for (my $rownum= 0; $rownum < scalar(@$country_table); $rownum++){ my $row= $country_table->[$rownum];

$evaluted++; if ($row->{continent} eq "Asia") { $sorted++; push(@{$sort_buffer->{$row->{population}}}, $rownum); }}

my $sorted_buffer= filesort_single_column($sort_buffer);

foreach my $rownum (@$sorted_buffer){ my $row= $country_table->[$rownum];

printf("%s\t%s\t%d\n", $row->{name}, $row->{continent}, $row->{population}); if (++$count >= 5) {last;}}

1行ずつフェッチして

scan_where_scan_orderby.pl

Page 33: Where狙いのキー、order by狙いのキー

テーブルスキャン

for (my $rownum= 0; $rownum < scalar(@$country_table); $rownum++){ my $row= $country_table->[$rownum];

$evaluted++; if ($row->{continent} eq "Asia") { $sorted++; push(@{$sort_buffer->{$row->{population}}}, $rownum); }}

my $sorted_buffer= filesort_single_column($sort_buffer);

foreach my $rownum (@$sorted_buffer){ my $row= $country_table->[$rownum];

printf("%s\t%s\t%d\n", $row->{name}, $row->{continent}, $row->{population}); if (++$count >= 5) {last;}}

WHERE句で評価

scan_where_scan_orderby.pl

Page 34: Where狙いのキー、order by狙いのキー

テーブルスキャン

for (my $rownum= 0; $rownum < scalar(@$country_table); $rownum++){ my $row= $country_table->[$rownum];

$evaluted++; if ($row->{continent} eq "Asia") { $sorted++; push(@{$sort_buffer->{$row->{population}}}, $rownum); }}

my $sorted_buffer= filesort_single_column($sort_buffer);

foreach my $rownum (@$sorted_buffer){ my $row= $country_table->[$rownum];

printf("%s\t%s\t%d\n", $row->{name}, $row->{continent}, $row->{population}); if (++$count >= 5) {last;}}

マッチしたらソートバッファに詰める

scan_where_scan_orderby.pl

Page 35: Where狙いのキー、order by狙いのキー

テーブルスキャン

for (my $rownum= 0; $rownum < scalar(@$country_table); $rownum++){ my $row= $country_table->[$rownum];

$evaluted++; if ($row->{continent} eq "Asia") { $sorted++; push(@{$sort_buffer->{$row->{population}}}, $rownum); }}

my $sorted_buffer= filesort_single_column($sort_buffer);

foreach my $rownum (@$sorted_buffer){ my $row= $country_table->[$rownum];

printf("%s\t%s\t%d\n", $row->{name}, $row->{continent}, $row->{population}); if (++$count >= 5) {last;}}

ソートバッファの中身をクイックソート

scan_where_scan_orderby.pl

Page 36: Where狙いのキー、order by狙いのキー

テーブルスキャン

for (my $rownum= 0; $rownum < scalar(@$country_table); $rownum++){ my $row= $country_table->[$rownum];

$evaluted++; if ($row->{continent} eq "Asia") { $sorted++; push(@{$sort_buffer->{$row->{population}}}, $rownum); }}

my $sorted_buffer= filesort_single_column($sort_buffer);

foreach my $rownum (@$sorted_buffer){ my $row= $country_table->[$rownum];

printf("%s\t%s\t%d\n", $row->{name}, $row->{continent}, $row->{population}); if (++$count >= 5) {last;}}

先頭の5件を出力したらそこでループを抜ける

scan_where_scan_orderby.pl

Page 37: Where狙いのキー、order by狙いのキー

テーブルスキャン

$ ./scan_where_scan_orderby.plMaldives Asia 286000Brunei Asia 328000Macao Asia 473000Qatar Asia 599000Bahrain Asia 617000Total rows evaluted are 239, sorted are 51.

scan_where_scan_orderby.pl

5行の出力に対して、239回のWHERE評価と51行のファイルソート

Page 38: Where狙いのキー、order by狙いのキー

そりゃあ遅いよテーブルスキャン

Page 39: Where狙いのキー、order by狙いのキー

インデックス作りましょう

Page 40: Where狙いのキー、order by狙いのキー

インデックス

$VAR1 = [ { 'Africa' => [ 2, 17, 19, .., ] }, { 'Antarctica' => [ 11, 12, 34, .., ] }, { 'Asia' => [ 1, 7, 9, ..,

カラムの値をkey,対応する行のプライマリーキーの

arrayrefを valueにしたhashrefの arrayref

(ソート済みを表現したかった)

display_single_column_index_structure.pl

Page 41: Where狙いのキー、order by狙いのキー

インデックス

$VAR1 = [ { 'Africa' => [ 2, 17, 19, .., ] }, { 'Antarctica' => [ 11, 12, 34, .., ] }, { 'Asia' => [ 1, 7, 9, ..,

display_single_column_index_structure.pl

Continent = 'Africa'な行IDを取り出すには

$index->[0]->{Africa}(arrayrefとして取り出す)

Page 42: Where狙いのキー、order by狙いのキー

インデックス

$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]]

Page 43: Where狙いのキー、order by狙いのキー

ちょっとつらい

$index->[$n]->{hoge}ここがマジックナンバー

Page 44: Where狙いのキー、order by狙いのキー

インデックス

$VAR1 = { 'map' => { 'Oceania' => 5, 'North America' => 4, 'Europe' => 3, 'South America' => 6, 'Asia' => 2, 'Africa' => 0, 'Antarctica' => 1 }, 'index' => [ [ 2, 17, 19, .. ], [ 11, 12, 34, .. ], ..

ソート済みの順番を$index->{map}に押し込めた

display_single_column_index_structure.pl

こっちはarrayrefの arrayrefに

Page 45: Where狙いのキー、order by狙いのキー

$VAR1 = [ { 'population' => '103000', 'region' => 'Caribbean', 'name' => 'Aruba', 'continent' => 'North America', 'code' => 'ABW', .. }, { 'population' => '22720000', 'region' => 'Southern and Central Asia', 'name' => 'Afghanistan', 'continent' => 'Asia', 'code' => 'AFG', .. }, { 'population' => '12878000', 'region' => 'Central Africa', 'name' => 'Angola', 'continent' => 'Africa', 'code' => 'AGO', .. }, ..

インデックス

$VAR1 = { 'map' => { 'Oceania' => 5, 'North America' => 4, 'Europe' => 3, 'South America' => 6, 'Asia' => 2, 'Africa' => 0, 'Antarctica' => 1 }, 'index' => [ [ 2, 17, 19, .. ], [ 11, 12, 34, .. ], ..

display_single_column_index_structure.pl

$index_num= $index->{map}->{Africa}

$rownum_array= $index->{index}->[$index_num}

foreach my $rownum(@$rownum_array){ my $row= $table->[$rownum]; ..}

Page 46: Where狙いのキー、order by狙いのキー

うわあ。。

ごめんなさい。。

Page 47: Where狙いのキー、order by狙いのキー

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件取り出す

Page 48: Where狙いのキー、order by狙いのキー

WHERE狙いのキー

my $index_num = $country_index->{map}->{Asia};my $rownum_array= $country_index->{index}->[$index_num];

foreach my $rownum (@$rownum_array){ my $row= $country_table->[$rownum];

$sorted++; push(@{$sort_buffer->{$row->{population}}}, $rownum);}

my $sorted_buffer= filesort_single_column($sort_buffer);

foreach my $rownum (@$sorted_buffer){ my $row= $country_table->[$rownum];

printf("%s\t%s\t%d\n", $row->{name}, $row->{continent}, $row->{population}); if (++$count >= 5) {last;}}

indexed_where_scan_orderby.pl

Page 49: Where狙いのキー、order by狙いのキー

WHERE狙いのキー

my $index_num = $country_index->{map}->{Asia};my $rownum_array= $country_index->{index}->[$index_num];

foreach my $rownum (@$rownum_array){ my $row= $country_table->[$rownum];

$sorted++; push(@{$sort_buffer->{$row->{population}}}, $rownum);}

my $sorted_buffer= filesort_single_column($sort_buffer);

foreach my $rownum (@$sorted_buffer){ my $row= $country_table->[$rownum];

printf("%s\t%s\t%d\n", $row->{name}, $row->{continent}, $row->{population}); if (++$count >= 5) {last;}}

WHERE句のレンジをインデックスから取り出す

indexed_where_scan_orderby.pl

Page 50: Where狙いのキー、order by狙いのキー

WHERE狙いのキー

my $index_num = $country_index->{map}->{Asia};my $rownum_array= $country_index->{index}->[$index_num];

foreach my $rownum (@$rownum_array){ my $row= $country_table->[$rownum];

$sorted++; push(@{$sort_buffer->{$row->{population}}}, $rownum);}

my $sorted_buffer= filesort_single_column($sort_buffer);

foreach my $rownum (@$sorted_buffer){ my $row= $country_table->[$rownum];

printf("%s\t%s\t%d\n", $row->{name}, $row->{continent}, $row->{population}); if (++$count >= 5) {last;}}

取り出した行を全部ソートバッファに詰めて

indexed_where_scan_orderby.pl

Page 51: Where狙いのキー、order by狙いのキー

WHERE狙いのキー

my $index_num = $country_index->{map}->{Asia};my $rownum_array= $country_index->{index}->[$index_num];

foreach my $rownum (@$rownum_array){ my $row= $country_table->[$rownum];

$sorted++; push(@{$sort_buffer->{$row->{population}}}, $rownum);}

my $sorted_buffer= filesort_single_column($sort_buffer);

foreach my $rownum (@$sorted_buffer){ my $row= $country_table->[$rownum];

printf("%s\t%s\t%d\n", $row->{name}, $row->{continent}, $row->{population}); if (++$count >= 5) {last;}}

indexed_where_scan_orderby.pl

クイックソートして

Page 52: Where狙いのキー、order by狙いのキー

WHERE狙いのキー

my $index_num = $country_index->{map}->{Asia};my $rownum_array= $country_index->{index}->[$index_num];

foreach my $rownum (@$rownum_array){ my $row= $country_table->[$rownum];

$sorted++; push(@{$sort_buffer->{$row->{population}}}, $rownum);}

my $sorted_buffer= filesort_single_column($sort_buffer);

foreach my $rownum (@$sorted_buffer){ my $row= $country_table->[$rownum];

printf("%s\t%s\t%d\n", $row->{name}, $row->{continent}, $row->{population}); if (++$count >= 5) {last;}}

indexed_where_scan_orderby.pl

先頭の5件を出力したらループを抜ける

Page 53: Where狙いのキー、order by狙いのキー

WHERE狙いのキー

$ ./indexed_where_scan_orderby.plMaldives Asia 286000Brunei Asia 328000Macao Asia 473000Qatar Asia 599000Bahrain Asia 617000Total rows evaluted are 1, sorted are 51.

indexed_where_scan_orderby.pl

WHEREの評価は1回で済むけど51行をファイルソート

Page 54: Where狙いのキー、order by狙いのキー

次はORDER BY狙い

Page 55: Where狙いのキー、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], ..]

Page 56: Where狙いのキー、order by狙いのキー

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件データが揃ったらループから抜ける

Page 57: Where狙いのキー、order by狙いのキー

ORDER BY狙いのキー

my $cardinality= scalar(keys(%{$index->{map}}));LOOP: for (my $index_num= 0; $index_num < $cardinality; $index_num++){ my $rownum_array= $index->{index}->[$index_num];

foreach my $rownum (@$rownum_array) { my $row= $country_table->[$rownum];

$evaluted++; if ($row->{continent} eq "Asia") { printf("%s\t%s\t%d\n", $row->{name}, $row->{continent}, $row->{population}); if (++$count >= 5) {last LOOP;} } }}

scan_where_indexed_orderby.pl

Page 58: Where狙いのキー、order by狙いのキー

ORDER BY狙いのキー

my $cardinality= scalar(keys(%{$index->{map}}));LOOP: for (my $index_num= 0; $index_num < $cardinality; $index_num++){ my $rownum_array= $index->{index}->[$index_num];

foreach my $rownum (@$rownum_array) { my $row= $country_table->[$rownum];

$evaluted++; if ($row->{continent} eq "Asia") { printf("%s\t%s\t%d\n", $row->{name}, $row->{continent}, $row->{population}); if (++$count >= 5) {last LOOP;} } }}

インデックスに沿って行を取り出して

scan_where_indexed_orderby.pl

Page 59: Where狙いのキー、order by狙いのキー

ORDER BY狙いのキー

my $cardinality= scalar(keys(%{$index->{map}}));LOOP: for (my $index_num= 0; $index_num < $cardinality; $index_num++){ my $rownum_array= $index->{index}->[$index_num];

foreach my $rownum (@$rownum_array) { my $row= $country_table->[$rownum];

$evaluted++; if ($row->{continent} eq "Asia") { printf("%s\t%s\t%d\n", $row->{name}, $row->{continent}, $row->{population}); if (++$count >= 5) {last LOOP;} } }}

WHERE句にマッチするか判定して

scan_where_indexed_orderby.pl

Page 60: Where狙いのキー、order by狙いのキー

ORDER BY狙いのキー

my $cardinality= scalar(keys(%{$index->{map}}));LOOP: for (my $index_num= 0; $index_num < $cardinality; $index_num++){ my $rownum_array= $index->{index}->[$index_num];

foreach my $rownum (@$rownum_array) { my $row= $country_table->[$rownum];

$evaluted++; if ($row->{continent} eq "Asia") { printf("%s\t%s\t%d\n", $row->{name}, $row->{continent}, $row->{population}); if (++$count >= 5) {last LOOP;} } }}

先頭の5件を出力したらそこでループを抜ける

scan_where_indexed_orderby.pl

Page 61: Where狙いのキー、order by狙いのキー

ORDER BY狙いのキー

$ ./scan_where_indexed_orderby.plMaldives Asia 286000Brunei Asia 328000Macao Asia 473000Qatar Asia 599000Bahrain Asia 617000Total rows evaluted are 79, sorted are 0.

scan_where_indexed_orderby.pl

ソートのオーバーヘッドはないものの、79行をWHERE句評価

Page 62: Where狙いのキー、order by狙いのキー

WHEREと ORDER BYを両方カバーするキー

Page 63: Where狙いのキー、order by狙いのキー

複合インデックス

$VAR1 = [ { 'Africa' => [ { '0' => [ 100 ] }, { '6000' => [ 188 ] }, .. ] }, { 'Antarctica' => [ { '0' => [ 11, 12, 34, 93, 187 ] } ] }, .. display_double_column_index_structure.pl

Page 64: Where狙いのキー、order by狙いのキー

複合インデックス

$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

Page 65: Where狙いのキー、order by狙いのキー

複合インデックス

$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

Page 66: Where狙いのキー、order by狙いのキー

複合インデックス

$VAR1 = { 'map' => { 'Oceania' => { '83000' => 12, '3862000' => 22, '235000' => 19, 'self' => 5, .. }, 'North America' => { '154000' => 14, '21000' => 4, .. }, } }, 'index' => [ [ [ 100 ], [ 188 ], ..

display_double_column_index_structure.pl

マジックナンバーよけをしたらかなりカオス

selfを予約語にしてるのがものすごくイケてない

Page 67: Where狙いのキー、order by狙いのキー

複合インデックス

$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 ..

Page 68: Where狙いのキー、order by狙いのキー

複合インデックス

$VAR1 = { 'map' => { 'Oceania' => { '83000' => 12, '3862000' => 22, '235000' => 19, 'self' => 5, .. }, 'North America' => { '154000' => 14, '21000' => 4, .. }, } }, 'index' => [ [ [ 100 ], [ 188 ], ..

display_double_column_index_structure.pl

my $index_nums_hash= $index->{map}->{Oceania}->{self};

foreach my $index_num (values(%$index_nums_hash))..

Foreach ..

Page 69: Where狙いのキー、order by狙いのキー

\カオス!/

すいませんすいません。。

Page 70: Where狙いのキー、order by狙いのキー

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件データが揃ったらループから抜ける

Page 71: Where狙いのキー、order by狙いのキー

WHEREと ORDER BYを両方カバーするキー

my $index_num = $country_index->{map}->{Asia}->{self};my $index_range= $country_index->{index}->[$index_num];

LOOP: foreach my $rownum_array (@$index_range){ foreach my $rownum (@$rownum_array) { my $row= $country_table->[$rownum];

printf("%s\t%s\t%d\n", $row->{name}, $row->{continent}, $row->{population}); if (++$count >= 5) {last LOOP;} }}

indexed_where_indexed_orderby.pl

Page 72: Where狙いのキー、order by狙いのキー

WHEREと ORDER BYを両方カバーするキー

my $index_num = $country_index->{map}->{Asia}->{self};my $index_range= $country_index->{index}->[$index_num];

LOOP: foreach my $rownum_array (@$index_range){ foreach my $rownum (@$rownum_array) { my $row= $country_table->[$rownum];

printf("%s\t%s\t%d\n", $row->{name}, $row->{continent}, $row->{population}); if (++$count >= 5) {last LOOP;} }}

WHERE句のレンジをインデックスから取り出す

indexed_where_indexed_orderby.pl

Page 73: Where狙いのキー、order by狙いのキー

WHEREと ORDER BYを両方カバーするキー

my $index_num = $country_index->{map}->{Asia}->{self};my $index_range= $country_index->{index}->[$index_num];

LOOP: foreach my $rownum_array (@$index_range){ foreach my $rownum (@$rownum_array) { my $row= $country_table->[$rownum];

printf("%s\t%s\t%d\n", $row->{name}, $row->{continent}, $row->{population}); if (++$count >= 5) {last LOOP;} }}

インデックスに沿って行を取り出して

indexed_where_indexed_orderby.pl

Page 74: Where狙いのキー、order by狙いのキー

WHEREと ORDER BYを両方カバーするキー

my $index_num = $country_index->{map}->{Asia}->{self};my $index_range= $country_index->{index}->[$index_num];

LOOP: foreach my $rownum_array (@$index_range){ foreach my $rownum (@$rownum_array) { my $row= $country_table->[$rownum];

printf("%s\t%s\t%d\n", $row->{name}, $row->{continent}, $row->{population}); if (++$count >= 5) {last LOOP;} }}

先頭の5件を出力したらそこでループを抜ける

indexed_where_indexed_orderby.pl

Page 75: Where狙いのキー、order by狙いのキー

というのを踏まえて

Page 76: Where狙いのキー、order by狙いのキー

MySQLは JOINが遅い( キリッ

Page 77: Where狙いのキー、order by狙いのキー

見ていきましょう

Page 78: Where狙いのキー、order by狙いのキー

スキャンジョイン

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件取り出す

Page 79: Where狙いのキー、order by狙いのキー

スキャンジョイン

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

Page 80: Where狙いのキー、order by狙いのキー

スキャンジョイン

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

Page 81: Where狙いのキー、order by狙いのキー

スキャンジョイン

for (my $language_rownum= 0; $language_rownum < scalar(@$language_table); $language_rownum++){ my $language_row= $language_table->[$language_rownum];

for (my $country_rownum= 0; $country_rownum < scalar(@$country_table); $country_rownum++) { my $country_row= $country_table->[$country_rownum];

$evaluted++; if ($language_row->{countrycode} eq $country_row->{code} && $country_row->{continent} eq "Asia") { $sorted++; push(@{$sort_buffer->{$language_row->{percentage}}}, [$language_rownum, $country_rownum]); } }}

ファイルソート以降は略

scan_join_scan_where_scan_orderby.pl

内側のテーブルでも1行ずつフェッチして

Page 82: Where狙いのキー、order by狙いのキー

スキャンジョイン

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

評価&ソートバッファ

Page 83: Where狙いのキー、order by狙いのキー

スキャンジョイン

$ ./scan_join_scan_where_scan_orderby.plUnited Arab Emirates Hindi 2441000 0.000000Bahrain English 617000 0.000000Japan Ainu 126714000 0.000000Kuwait English 1972000 0.000000Lebanon French 3282000 0.000000Total rows evaluted are 235176, sorted are 239.

評価する行の数が倍倍ゲェム

scan_join_scan_where_scan_orderby.pl

Page 84: Where狙いのキー、order by狙いのキー

しねばいいのに

Page 85: Where狙いのキー、order by狙いのキー

直感の赴くまま

SELECT Name, Language, Population, PercentageFROM Country INNER JOIN CountryLanguage ON Country.Code= CountryLanguage.CountryCodeWHERE Country.continent = 'Asia'ORDER BY CountryLanguage.Percentage LIMIT 5;

mysql56> ALTER TABLE Country ADD KEY index_continent(continent);Query OK, 0 rows affected (0.05 sec)Records: 0 Duplicates: 0 Warnings: 0

mysql56> ALTER TABLE CountryLanguage -> ADD KEY index_countrycode_percentage(countrycode, percentage);Query OK, 0 rows affected (0.11 sec)Records: 0 Duplicates: 0 Warnings: 0

これは残念ながらWHERE狙いのキーになる。わかりますん。

Page 86: Where狙いのキー、order by狙いのキー

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件取り出す

Page 87: Where狙いのキー、order by狙いのキー

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

Page 88: Where狙いのキー、order by狙いのキー

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

Page 89: Where狙いのキー、order by狙いのキー

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

Page 90: Where狙いのキー、order by狙いのキー

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

Page 91: Where狙いのキー、order by狙いのキー

JOIN de WHERE狙いのキー

$ ./indexed_join_indexed_where_scan_orderby.plUnited Arab Emirates Hindi 2441000 0.000000Bahrain English 617000 0.000000Japan Ainu 126714000 0.000000Kuwait English 1972000 0.000000Lebanon French 3282000 0.000000Total rows evaluted are 52, sorted are 239.

スキャンジョインよりよっぽどいいけどそんなに効率が良いわけではなさそう

indexed_join_indexed_where_scan_orderby.pl

Page 92: Where狙いのキー、order by狙いのキー

percentageをインデックスに入れてるのにWHERE狙いのキー?

Page 93: Where狙いのキー、order by狙いのキー

JOIN de WHERE狙いのキーmysql56> EXPLAIN SELECT Name, Language, Population, Percentage -> FROM Country INNER JOIN CountryLanguage ON Country.Code= CountryLanguage.CountryCode -> WHERE Country.continent = 'Asia' -> ORDER BY Percentage LIMIT 5;+----+-------------+-----------------+------+---------------+------+---------+------+------+----------------------------------------------------+| id | select_type | table | type | possible_keys | key | key_len | ref | rows | Extra |+----+-------------+-----------------+------+---------------+------+---------+------+------+----------------------------------------------------+| 1 | SIMPLE | CountryLanguage | ALL | NULL | NULL | NULL | NULL | 984 | Using temporary; Using filesort || 1 | SIMPLE | Country | ALL | NULL | NULL | NULL | NULL | 239 | Using where; Using join buffer (Block Nested Loop) |+----+-------------+-----------------+------+---------------+------+---------+------+------+----------------------------------------------------+2 rows in set (0.00 sec)

mysql56> EXPLAIN SELECT Name, Language, Population, Percentage -> FROM Country INNER JOIN CountryLanguage ON Country.Code= CountryLanguage.CountryCode -> WHERE Country.continent = 'Asia' -> ORDER BY Percentage LIMIT 5;+----+-------------+-----------------+------+------------------------------+------------------------------+---------+--------------------+------+--------------------------------------------------------+| id | select_type | table | type | possible_keys | key | key_len | ref | rows | Extra |+----+-------------+-----------------+------+------------------------------+------------------------------+---------+--------------------+------+--------------------------------------------------------+| 1 | SIMPLE | Country | ref | index_continent | index_continent | 33 | const | 51 | Using index condition; Using temporary; Using filesort || 1 | SIMPLE | CountryLanguage | ref | index_countrycode_percentage | index_countrycode_percentage | 3 | world.Country.Code | 2 | NULL |+----+-------------+-----------------+------+------------------------------+------------------------------+---------+--------------------+------+--------------------------------------------------------+2 rows in set (0.00 sec)

* 外部表と内部表が入れ替わってる* ORDER BYをインデックスで解決するには、 そのカラムが外部表にあることが必要

Page 94: Where狙いのキー、order by狙いのキー

Let's プリントデバッグ

Page 95: Where狙いのキー、order by狙いのキー

JOIN de WHERE狙いのキー

foreach my $country_rownum (@$country_rownum_array){ my $country_row= $country_table->[$country_rownum];

$evaluted++; my $language_index_num = $language_index->{map}->{$country_row->{code}}->{self}; my $language_index_range= $language_index->{index}->[$language_index_num];

foreach my $language_rownum_array (@$language_index_range) { foreach my $language_rownum (@$language_rownum_array) { my $language_row= $language_table->[$language_rownum];

$sorted++; printf("* %s\t%d\t%s => %s\t%f\n", $country_row->{name}, $country_row->{population}, $country_row->{code}, $language_row->{language}, $language_row->{percentage}); } }}

verbose_indexed_join_indexed_where_scan_orderby.pl

Page 96: Where狙いのキー、order by狙いのキー

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

Page 97: Where狙いのキー、order by狙いのキー

* Afghanistan 22720000 AFG => AFG Balochi 0.900000* Afghanistan 22720000 AFG => AFG Turkmenian 1.900000* Afghanistan 22720000 AFG => AFG Uzbek 8.800000* Afghanistan 22720000 AFG => AFG Dari 32.100000* Afghanistan 22720000 AFG => AFG Pashto 52.400000* United Arab Emirates 2441000 ARE => ARE Hindi 0.000000* United Arab Emirates 2441000 ARE => ARE Arabic 42.000000* Armenia 3520000 ARM => ARM Azerbaijani 2.600000* Armenia 3520000 ARM => ARM Armenian 93.400000* Azerbaijan 7734000 AZE => AZE Armenian 2.000000* Azerbaijan 7734000 AZE => AZE Lezgian 2.300000* Azerbaijan 7734000 AZE => AZE Russian 3.000000* Azerbaijan 7734000 AZE => AZE Azerbaijani 89.000000* Bangladesh 129155000 BGD => BGD Garo 0.100000* Bangladesh 129155000 BGD => BGD Khasi 0.100000* Bangladesh 129155000 BGD => BGD Santhali 0.100000* Bangladesh 129155000 BGD => BGD Tripuri 0.100000* Bangladesh 129155000 BGD => BGD Marma 0.200000* Bangladesh 129155000 BGD => BGD Chakma 0.400000* Bangladesh 129155000 BGD => BGD Bengali 97.700000* Bahrain 617000 BHR => BHR English 0.000000* Bahrain 617000 BHR => BHR Arabic 67.700000* Brunei 328000 BRN => BRN English 3.100000* Brunei 328000 BRN => BRN Chinese 9.300000* Brunei 328000 BRN => BRN Malay-English 28.800000* Brunei 328000 BRN => BRN Malay 45.500000..

JOIN de WHERE狙いのキー

verbose_indexed_join_indexed_where_scan_orderby.pl

こっちがCountryこっちがCountryLanguage

Page 98: Where狙いのキー、order by狙いのキー

確かにソートが必要になる

Page 99: Where狙いのキー、order by狙いのキー

ORDER BY狙いのキーを使うにはどちらが外部表で

どう結合されるかをイメージ

Page 100: Where狙いのキー、order by狙いのキー

JOIN de WHERE狙いのキー

* 0.000000 Hindi ARE => ARE United Arab Emirates 2441000* 0.000000 English BHR => BHR Bahrain 617000* 0.000000 Ainu JPN => JPN Japan 126714000* 0.000000 English KWT => KWT Kuwait 1972000* 0.000000 French LBN => LBN Lebanon 3282000* 0.000000 English MDV => MDV Maldives 286000* 0.000000 Balochi OMN => OMN Oman 2542000* 0.000000 Urdu QAT => QAT Qatar 599000* 0.000000 Portuguese TMP => TMP East Timor 885000* 0.000000 Sunda TMP => TMP East Timor 885000* 0.000000 Soqutri YEM => YEM Yemen 18112000* 0.100000 Garo BGD => BGD Bangladesh 129155000* 0.100000 Khasi BGD => BGD Bangladesh 129155000* 0.100000 Santhali BGD => BGD Bangladesh 129155000* 0.100000 Tripuri BGD => BGD Bangladesh 129155000* 0.100000 English JPN => JPN Japan 126714000* 0.100000 Philippene Languages JPN => JPN Japan 126714000* 0.100000 Chinese KOR => KOR South Korea 46844000* 0.100000 Chinese PRK => PRK North Korea 24039000* 0.200000 Marma BGD => BGD Bangladesh 129155000* 0.200000 Dong CHN => CHN China 1277558000* 0.200000 Puyi CHN => CHN China 1277558000* 0.200000 Chinese JPN => JPN Japan 126714000* 0.300000 Paiwan TWN => TWN Taiwan 22256000* 0.400000 Chakma BGD => BGD Bangladesh 129155000* 0.400000 Mongolian CHN => CHN China 1277558000..

verbose_indexed_join_indexed_where_indexed_orderby.pl

こっちがCountry

こっちがCountryLanguage

Page 101: Where狙いのキー、order by狙いのキー

JOIN de WHERE狙いのキー

* 0.000000 Hindi ARE => ARE United Arab Emirates 2441000* 0.000000 English BHR => BHR Bahrain 617000* 0.000000 Ainu JPN => JPN Japan 126714000* 0.000000 English KWT => KWT Kuwait 1972000* 0.000000 French LBN => LBN Lebanon 3282000* 0.000000 English MDV => MDV Maldives 286000* 0.000000 Balochi OMN => OMN Oman 2542000* 0.000000 Urdu QAT => QAT Qatar 599000* 0.000000 Portuguese TMP => TMP East Timor 885000* 0.000000 Sunda TMP => TMP East Timor 885000* 0.000000 Soqutri YEM => YEM Yemen 18112000* 0.100000 Garo BGD => BGD Bangladesh 129155000* 0.100000 Khasi BGD => BGD Bangladesh 129155000* 0.100000 Santhali BGD => BGD Bangladesh 129155000* 0.100000 Tripuri BGD => BGD Bangladesh 129155000* 0.100000 English JPN => JPN Japan 126714000* 0.100000 Philippene Languages JPN => JPN Japan 126714000* 0.100000 Chinese KOR => KOR South Korea 46844000* 0.100000 Chinese PRK => PRK North Korea 24039000* 0.200000 Marma BGD => BGD Bangladesh 129155000* 0.200000 Dong CHN => CHN China 1277558000* 0.200000 Puyi CHN => CHN China 1277558000* 0.200000 Chinese JPN => JPN Japan 126714000* 0.300000 Paiwan TWN => TWN Taiwan 22256000* 0.400000 Chakma BGD => BGD Bangladesh 129155000* 0.400000 Mongolian CHN => CHN China 1277558000..

verbose_indexed_join_indexed_where_indexed_orderby.pl

ここでソート済みであってほしいから

こっち向きに結合してほしい

Page 102: Where狙いのキー、order by狙いのキー

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行マッチしたら終了

Page 103: Where狙いのキー、order by狙いのキー

JOIN de ORDER BY狙いのキー

my $language_cardinality= scalar(keys(%{$language_index->{map}}));LOOP: for (my $language_index_num= 0; $language_index_num < $language_cardinality; $language_index_num++){ my $language_rownum_array= $language_index->{index}->[$language_index_num]; foreach my $language_rownum (@$language_rownum_array) { my $language_row= $language_table->[$language_rownum]; my $country_index_num_first= $country_index->{map}->{$language_row->{countrycode}}->{self}; my $country_index_num_second= $country_index->{map}->{$language_row->{countrycode}}->{Asia};

if (!(defined($country_index_num_second))) {next;}

my $country_rownum_array= $country_index->{index}->[$country_index_num_first]->[$country_index_num_second];

foreach my $country_rownum (@$country_rownum_array) { my $country_row= $country_table->[$country_rownum];

printf("%s\t%s\t%d\t%f\n", $country_row->{name}, $language_row->{language}, $country_row->{population}, $language_row->{percentage}); if (++$count >= 5) {last LOOP;} } }}

indexed_join_indexed_where_indexed_orderby.pl

Page 104: Where狙いのキー、order by狙いのキー

JOIN de ORDER BY狙いのキー

my $language_cardinality= scalar(keys(%{$language_index->{map}}));LOOP: for (my $language_index_num= 0; $language_index_num < $language_cardinality; $language_index_num++){ my $language_rownum_array= $language_index->{index}->[$language_index_num]; foreach my $language_rownum (@$language_rownum_array) { my $language_row= $language_table->[$language_rownum]; my $country_index_num_first= $country_index->{map}->{$language_row->{countrycode}}->{self}; my $country_index_num_second= $country_index->{map}->{$language_row->{countrycode}}->{Asia};

if (!(defined($country_index_num_second))) {next;}

my $country_rownum_array= $country_index->{index}->[$country_index_num_first]->[$country_index_num_second];

foreach my $country_rownum (@$country_rownum_array) { my $country_row= $country_table->[$country_rownum];

printf("%s\t%s\t%d\t%f\n", $country_row->{name}, $language_row->{language}, $country_row->{population}, $language_row->{percentage}); if (++$count >= 5) {last LOOP;} } }}

外側のテーブルにはORDER BY狙いのキー

indexed_join_indexed_where_indexed_orderby.pl

Page 105: Where狙いのキー、order by狙いのキー

JOIN de ORDER BY狙いのキー

my $language_cardinality= scalar(keys(%{$language_index->{map}}));LOOP: for (my $language_index_num= 0; $language_index_num < $language_cardinality; $language_index_num++){ my $language_rownum_array= $language_index->{index}->[$language_index_num]; foreach my $language_rownum (@$language_rownum_array) { my $language_row= $language_table->[$language_rownum]; my $country_index_num_first= $country_index->{map}->{$language_row->{countrycode}}->{self}; my $country_index_num_second= $country_index->{map}->{$language_row->{countrycode}}->{Asia};

if (!(defined($country_index_num_second))) {next;}

my $country_rownum_array= $country_index->{index}->[$country_index_num_first]->[$country_index_num_second];

foreach my $country_rownum (@$country_rownum_array) { my $country_row= $country_table->[$country_rownum];

printf("%s\t%s\t%d\t%f\n", $country_row->{name}, $language_row->{language}, $country_row->{population}, $language_row->{percentage}); if (++$count >= 5) {last LOOP;} } }}

内側のテーブルはWHERE狙いのキー

indexed_join_indexed_where_indexed_orderby.pl

Page 106: Where狙いのキー、order by狙いのキー

WHERE狙いのキーORDER BY狙いのキー

● もちろん両方狙えるインデックスを作っていくのが最良。● ただし必ずしも一番良いキーでWHEREと ORDER BYを両方狙い打ちできるとは限らない。

● どっちがどういう動作になるかがイメージできれば、打ち分けるのはそう難しくない

● ちゃんと狙ったクエリーとそうでないクエリーは100倍くらい性能が違うこともザラ。

Page 107: Where狙いのキー、order by狙いのキー

まとめ

● WHERE句で十分絞り込める場合はWHERE狙い● たとえば WHERE Continent = 'Moo'(マッチ0件 )だったらWHERE狙い。ORDER BY狙いだと5件揃わないのでテーブルをまるまるスキャンする。

● WHERE句がほとんど機能しないような場合はORDER BY狙い● 同じカラム同じカーディナリティーでも、値によって本当は違う。

● このセッションを聞いている人をWHERE gender = 'male'ならORDER BYを狙った方がいいだろうし、WHERE gender = 'female'ならWHEREを狙った方がいい。

Page 108: Where狙いのキー、order by狙いのキー

愚痴

● 本当はそのあたりの按配をオプティマイザー氏が上手くやってくれると嬉しい。● Oracle DBは上手くやってくれるらしい(って聞いた。ホントかどうかは知らない)

● MySQLのオプティマイザーが残念なのはInnoDBの統計情報のせいもある。– InnoDBの統計情報はサンプリング(デフォルトでは1インデックスあたり8ページ)

● 8ページ= 128kB、テーブルサイズがどれだけあろうとも。

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

Page 109: Where狙いのキー、order by狙いのキー

「何とかとMySQLは使いよう」

楽しいMySQLライフを!