91
MicroAd マイクロアド 薮下 和弥 システム開発部 MySQL 勉強会 クエリチューニング編

MySQL勉強会 クエリチューニング編

Embed Size (px)

DESCRIPTION

社内で実施したMySQLの勉強会資料です。 クエリチューニングメインの内容になっています。 B-TREEインデックスやソートの仕組み、相関サブクエリなんかを取り扱っています。

Citation preview

Page 1: MySQL勉強会 クエリチューニング編

MicroAd

マイクロアド

薮下 和弥 システム開発部

MySQL 勉強会

クエリチューニング編

Page 2: MySQL勉強会 クエリチューニング編

はじめに

こんな考え方をしていませんか?

Page 3: MySQL勉強会 クエリチューニング編

Page 4: MySQL勉強会 クエリチューニング編

当勉強会で…

これらの考え方が変わり、

クエリチューニングの見解が広がります!

Page 5: MySQL勉強会 クエリチューニング編

1章 対話的にクエリを作る

2章 オプティマイザの判断

3章 サブクエリは「データ」の集合

4章 ソートとインデックス

5章 インデックスは万能ではない

6章 相関サブクエリは諸刃の剣

休憩

Page 6: MySQL勉強会 クエリチューニング編

1章 対話的にクエリを作る

2章 オプティマイザの判断

3章 サブクエリは「データ」の集合

4章 ソートとインデックス

5章 インデックスは万能ではない

6章 相関サブクエリは諸刃の剣

休憩

Page 7: MySQL勉強会 クエリチューニング編

1章 対話的にクエリを作る

City:都市テーブル

+-------------+------------+----------+------+-----+ | ColumnID | ColumnName | Type | Null | Key | +-------------+------------+----------+------+-----+ | ID | 都市ID | int(11) | NO | PRI | | Name | 都市名 | char(35) | NO | | | CountryCode | 国コード | char(3) | NO | MUL | | District | 地区 | char(20) | NO | | | Population | 都市人口 | int(11) | NO | MUL | +-------------+------------+----------+------+-----+

+----------------+------------------+---------------------------------------------------------------------------------------+------+-----+ | ColumnID | ColumnName | Type | Null | Key | +----------------+------------------+---------------------------------------------------------------------------------------+------+-----+ | Code | 国コード | char(3) | NO | PRI | | Name | 国名 | char(52) | NO | | | Continent | 大陸 | enum('Asia','Europe','North America','Africa','Oceania','Antarctica','South America') | NO | | | Region | 地帯 | char(26) | NO | | | SurfaceArea | 国土面積 | float(10,2) | NO | | | IndepYear | 独立年 | smallint(6) | YES | | | Population | 国人口 | int(11) | NO | | | LifeExpectancy | 平均寿命 | float(3,1) | YES | | | GNP | 国民総生産 | float(10,2) | YES | | | GNPOld | 国民総生産(過去) | float(10,2) | YES | | | LocalName | 国名(ローカル) | char(45) | NO | | | GovernmentForm | 政治体系 | char(45) | NO | | | HeadOfState | 国家元首 | char(60) | YES | | | Capital | 首都コード | int(11) | YES | | | Code2 | 国コード(略) | char(2) | NO | | +----------------+------------------+---------------------------------------------------------------------------------------+------+-----+

Country:国テーブル

件数:4079

件数:239

当勉強会ではMySQLのサンプルデータベース内のテーブルを使用し、 実際にクエリを組んで見解を広げて頂きます。

Code ・ ・

Country

・ CountryCode

City

Page 8: MySQL勉強会 クエリチューニング編

Population / 2 +------------+ | Population | +------------+ | 4848150 | | 3990115 | | 4990810 | +------------+

1章 対話的にクエリを作る

抽出項目

都市名 国コード 都市人口

都市人口の半数が、 瀋陽の人口よりも多い都市

抽出条件

クエリ

SELECT Cty1.Name, Cty1.CountryCode, Cty1.Population FROM City Cty1 WHERE Cty.Population / 2 > ( SELECT Cty2.Population FROM City Cty2 WHERE Cty2.Name = 'Shenyang' );

City(都市テーブル) +-------------+------------+----------+-----+ | ColumnID | ColumnName | Type | Key | +-------------+------------+----------+-----+ | ID | 都市ID | int(11) | PRI | | Name | 都市名 | char(35) | | | CountryCode | 国コード | char(3) | MUL | | District | 地区 | char(20) | | | Population | 都市人口 | int(11) | MUL | +-------------+------------+----------+-----+

テーブル

抽出イメージ

Cty1(City) +------+----------+-------------+----------+------------+ | ID | Name | CountryCode | District | Population | +------+----------+-------------+----------+------------+ | 1890 | Shanghai | CHN | Shanghai | 9696300 | | 1532 | Tokyo | JPN | Tokyo-to | 7980230 | | 2331 | Seoul | KOR | Seoul | 9981620 | +------+----------+-------------+----------+------------+

瀋陽の人口 +------------+ | Population | +------------+ | 4265200 | +------------+

Cty1(City) +------+----------+-------------+----------+------------+ | ID | Name | CountryCode | District | Population | +------+----------+-------------+----------+------------+ | 1890 | Shanghai | CHN | Shanghai | 9696300 |○ | 1532 | Tokyo | JPN | Tokyo-to | 7980230 | | 2331 | Seoul | KOR | Seoul | 9981620 |○ +------+----------+-------------+----------+------------+

Cty1(City) +------+----------+-------------+----------+------------+ | ID | Name | CountryCode | District | Population | +------+----------+-------------+----------+------------+ | 1890 | Shanghai | CHN | Shanghai | 9696300 |○ | 1532 | Tokyo | JPN | Tokyo-to | 7980230 | | 2331 | Seoul | KOR | Seoul | 9981620 |○ +------+----------+-------------+----------+------------+

Population / 2 +------------+ | Population | +------------+ | 4848150 | | 3990115 | | 4990810 | +------------+

Page 9: MySQL勉強会 クエリチューニング編

1章 対話的にクエリを作る

完 ? +----------+-------------+------------+ | Name | CountryCode | Population | +----------+-------------+------------+ | Shanghai | CHN | 9696300 | | Seoul | KOR | 9981620 | +----------+-------------+------------+

実 行 結 果

Page 10: MySQL勉強会 クエリチューニング編

1章 対話的にクエリを作る

ちょっと待った

速いの?

このクエリ

Page 11: MySQL勉強会 クエリチューニング編

1章 対話的にクエリを作る

mysql> SELECT … WHERE Cty2.Name = 'Shenyang'); ・ ・ ・ 8 rows in set (0.00 sec)

速いッ!

Page 12: MySQL勉強会 クエリチューニング編

1章 対話的にクエリを作る

…それだけでは駄目です。

実行計画を取得してください。

Page 13: MySQL勉強会 クエリチューニング編

1章 対話的にクエリを作る

実行計画とは、 問い合わせ情報(クエリ)を基に、MySQLが内部的に立てる実行の計画(手順)です。

※MySQL 5.6からUPDATE、DELETE、INSERTも実行計画取得が可能

EXPLAIN select_query;

実行計画取得コマンド

言わば、

なのです。

MySQLの意思表示

Page 14: MySQL勉強会 クエリチューニング編

1章 対話的にクエリを作る

先程作成したクエリの実行計画を確認してみましょう。

+----+-------------+-------+------+---------------+------+---------+------+------+-------------+ | id | select_type | table | type | possible_keys | key | key_len | ref | rows | Extra | +----+-------------+-------+------+---------------+------+---------+------+------+-------------+ | 1 | PRIMARY | Cty1 | ALL | NULL | NULL | NULL | NULL | 4051 | Using where | | 2 | SUBQUERY | Cty2 | ALL | NULL | NULL | NULL | NULL | 4051 | Using where | +----+-------------+-------+------+---------------+------+---------+------+------+-------------+

EXPLAIN SELECT Cty1.CountryCode, Cty1.Name, Cty1.Population FROM City Cty1 WHERE Cty1.Population / 2 > ( SELECT Cty2.Population FROM City Cty2 WHERE Cty2.Name = 'Shenyang' );

クエリ

実行計画

+----+-------------+-------+------+---------------+------+---------+------+------+-------------+ | id | select_type | table | type | possible_keys | key | key_len | ref | rows | Extra | +----+-------------+-------+------+---------------+------+---------+------+------+-------------+ | 1 | PRIMARY | Cty1 | ALL | NULL | NULL | NULL | NULL | 4051 | Using where | | 2 | SUBQUERY | Cty2 | ALL | NULL | NULL | NULL | NULL | 4051 | Using where | +----+-------------+-------+------+---------------+------+---------+------+------+-------------+

INDEXが使われてない!テーブルスキャン!!

外部クエリ

サブクエリ

Page 15: MySQL勉強会 クエリチューニング編

1章 対話的にクエリを作る

何がダメなの? |ω・)

Page 16: MySQL勉強会 クエリチューニング編

1章 対話的にクエリを作る

例えばこんなクエリ。

SELECT * FROM City WHERE Name = 'Tokyo'; City(都市テーブル) 件数:4079 +-------------+------------+-----+ | ColumnID | ColumnName | Key | +-------------+------------+-----+ | ID | 都市ID | PRI | | Name | 都市名 | | | CountryCode | 国コード | MUL | | District | 地区 | | | Population | 都市人口 | MUL | +-------------+------------+-----+

row75

row1

row1001

row561

row314

row245

row843

row48

row1831 row181 row468

row188 row2188

row821

row87

row987

row456

row6

row236

row4000

row813

row3 row30

row209

row131 row2430

row2120

row21

row2901

row333

row713

row3557

row1684

row338

row3388

row1088

row10

row1540

row3140 row1059 row193

row412

row198

row15

row681

row1926

row1985

row2792

row961

row461

row1563

row3154

row1863

row731

row462

row1462

row3311

row1991

row1535

row501

row852

row3841

row100

row1091

row491

row481

row584

row522

row521

row64

row487

row983

row1616

row3731

City

row737

row1379

row3838

row1103

row588

row519

row819 row4009

row12

Cityの行を一つ一つを確認して、Nameが'Tokyo'の行を探してください。

Page 17: MySQL勉強会 クエリチューニング編

1章 対話的にクエリを作る

これが、テーブルスキャン。

Page 18: MySQL勉強会 クエリチューニング編

1章 対話的にクエリを作る

INDEXを付けるとどうなるの? |ω・)

Page 19: MySQL勉強会 クエリチューニング編

1章 対話的にクエリを作る

SELECT * FROM City WHERE Name = 'Tokyo'; City(都市テーブル) 件数:4079 +-------------+------------+-----+ | ColumnID | ColumnName | Key | +-------------+------------+-----+ | ID | 都市ID | PRI | | Name | 都市名 | MUL | | CountryCode | 国コード | MUL | | District | 地区 | | | Population | 都市人口 | MUL | +-------------+------------+-----+

row1863

City

NameカラムのINDEX情報

'Tokyo'

A - K L - Z

A - D E - G H - I

L - O P - R S - Z

Peking Pusan Qazvin

Rotterdam Roma

London Liverpool Mexico New York Okinawa

Sydney Tokyo

Washington Valera

Zelenograd

… … …

A - K L - Z

L - O P - R S - Z

Sydney Tokyo

Washington Valera

Zelenograd

Page 20: MySQL勉強会 クエリチューニング編

1章 対話的にクエリを作る

これが、INDEXッ!!

Page 21: MySQL勉強会 クエリチューニング編

+-------------+------------+----------+------+-----+ | ColumnID | ColumnName | Type | Null | Key | +-------------+------------+----------+------+-----+ | ID | 都市ID | int(11) | NO | PRI | | Name | 都市名 | char(35) | NO | | | CountryCode | 国コード | char(3) | NO | MUL | | District | 地区 | char(20) | NO | | | Population | 都市人口 | int(11) | NO | MUL | +-------------+------------+----------+------+-----+

1章 対話的にクエリを作る

MySQLには是非INDEXを使って頂きたい。 サブクエリからチューニングしてみます。

都市人口の半分が瀋陽の人口よりも多い都市

抽出仕様

City:都市テーブル

+-------------+------------+----------+------+-----+ | ColumnID | ColumnName | Type | Null | Key | +-------------+------------+----------+------+-----+ | ID | 都市ID | int(11) | NO | PRI | | Name | 都市名 | char(35) | NO | | | CountryCode | 国コード | char(3) | NO | MUL | | District | 地区 | char(20) | NO | | | Population | 都市人口 | int(11) | NO | MUL | +-------------+------------+----------+------+-----+

サブクエリ

SELECT Cty1.Name, Cty1.CountryCode, Cty1.Population FROM City Cty1 WHERE Cty1.Population / 2 > ( SELECT Cty2.Population FROM City Cty2 WHERE Cty2.Name = 'Shenyang' );

都市人口の半分が中国の瀋陽の人口よりも多い都市

瀋陽が中国の都市であることは明確であり、 抽出条件に国コードが追加されても問題はない。

SELECT Cty1.Name, Cty1.CountryCode, Cty1.Population FROM City Cty1 WHERE Cty1.Population / 2 > ( SELECT Cty2.Population FROM City Cty2 WHERE AND Cty2.Name = 'Shenyang' );

SELECT Cty1.Name, Cty1.CountryCode, Cty1.Population FROM City Cty1 WHERE Cty1.Population / 2 > ( SELECT Cty2.Population FROM City Cty2 WHERE Cty2.CountryCode = 'CHN' AND Cty2.Name = 'Shenyang' );

Page 22: MySQL勉強会 クエリチューニング編

+----+-------------+-------+------+---------------+------+---------+------+------+-------------+ | id | select_type | table | type | possible_keys | key | key_len | ref | rows | Extra | +----+-------------+-------+------+---------------+------+---------+------+------+-------------+ | 1 | PRIMARY | Cty1 | ALL | NULL | NULL | NULL | NULL | 4051 | Using where | | 2 | SUBQUERY | Cty2 | ALL | NULL | NULL | NULL | NULL | 4051 | Using where | +----+-------------+-------+------+---------------+------+---------+------+------+-------------+

1章 対話的にクエリを作る

修正後の実行計画は…

+----+-------------+-------+------+---------------+-------------+---------+-------+------+-------------+ | id | select_type | table | type | possible_keys | key | key_len | ref | rows | Extra | +----+-------------+-------+------+---------------+-------------+---------+-------+------+-------------+ | 1 | PRIMARY | Cty1 | ALL | NULL | NULL | NULL | NULL | 4051 | Using where | | 2 | SUBQUERY | Cty2 | ref | CountryCode | CountryCode | 3 | const | 363 | Using where | +----+-------------+-------+------+---------------+-------------+---------+-------+------+-------------+

SELECT Cty1.CountryCode, Cty1.Name, Cty1.Population FROM City Cty1 WHERE Cty1.Population / 2 > ( SELECT Cty2.Population FROM City Cty2 WHERE Cty2.CountryCode = 'CHN' AND Cty2.Name = 'Shenyang' );

クエリ

チューニング前の実行計画

サブクエリの検索条件にCountryCodeを指定したことで、INDEXを利用した検索が可能に!

チューニング後の実行計画

Page 23: MySQL勉強会 クエリチューニング編

1章 対話的にクエリを作る

次は外部クエリ。

+----+-------------+-------+------+---------------+-------------+---------+-------+------+-------------+ | id | select_type | table | type | possible_keys | key | key_len | ref | rows | Extra | +----+-------------+-------+------+---------------+-------------+---------+-------+------+-------------+ | 1 | PRIMARY | Cty1 | ALL | NULL | NULL | NULL | NULL | 4051 | Using where | | 2 | SUBQUERY | Cty2 | ref | CountryCode | CountryCode | 3 | const | 363 | Using where | +----+-------------+-------+------+---------------+-------------+---------+-------+------+-------------+

City:都市テーブル

+-------------+------------+----------+------+-----+ | ColumnID | ColumnName | Type | Null | Key | +-------------+------------+----------+------+-----+ | ID | 都市ID | int(11) | NO | PRI | | Name | 都市名 | char(35) | NO | | | CountryCode | 国コード | char(3) | NO | MUL | | District | 地区 | char(20) | NO | | | Population | 都市人口 | int(11) | NO | MUL | +-------------+------------+----------+------+-----+

クエリ

実行計画

SELECT Cty1.CountryCode, Cty1.Name, Cty1.Population FROM City Cty1 WHERE Cty1.Population / 2 > ( SELECT Cty2.Population FROM City Cty2 WHERE Cty2.CountryCode = 'CHN' AND Cty2.Name = 'Shenyang' );

あれ?! INDEXは?!

SELECT Cty1.CountryCode, Cty1.Name, Cty1.Population FROM City Cty1 WHERE Cty1.Population / 2 > ( SELECT Cty2.Population FROM City Cty2 WHERE Cty2.CountryCode = 'CHN' AND Cty2.Name = 'Shenyang' );

+-------------+------------+----------+------+-----+ | ColumnID | ColumnName | Type | Null | Key | +-------------+------------+----------+------+-----+ | ID | 都市ID | int(11) | NO | PRI | | Name | 都市名 | char(35) | NO | | | CountryCode | 国コード | char(3) | NO | MUL | | District | 地区 | char(20) | NO | | | Population | 都市人口 | int(11) | NO | MUL | +-------------+------------+----------+------+-----+

+----+-------------+-------+------+---------------+-------------+---------+-------+------+-------------+ | id | select_type | table | type | possible_keys | key | key_len | ref | rows | Extra | +----+-------------+-------+------+---------------+-------------+---------+-------+------+-------------+ | 1 | PRIMARY | Cty1 | ALL | NULL | NULL | NULL | NULL | 4051 | Using where | | 2 | SUBQUERY | Cty2 | ref | CountryCode | CountryCode | 3 | const | 363 | Using where | +----+-------------+-------+------+---------------+-------------+---------+-------+------+-------------+

Page 24: MySQL勉強会 クエリチューニング編

1章 対話的にクエリを作る

WHERE句内をよく見てください。

クエリ

SELECT Cty1.CountryCode, Cty1.Name, Cty1.Population FROM City Cty1 WHERE Cty1.Population / 2 > ( SELECT Cty2.Population FROM City Cty2 WHERE Cty2.CountryCode = 'CHN' AND Cty2.Name = 'Shenyang' );

SELECT Cty1.CountryCode, Cty1.Name, Cty1.Population FROM City Cty1 WHERE Cty1.Population / 2 > ( SELECT Cty2.Population FROM City Cty2 WHERE Cty2.CountryCode = 'CHN' AND Cty2.Name = 'Shenyang' );

索引列に対して演算処理を行っている場合、 オプティマイザはINDEXを使用することが出来ないのです。

Page 25: MySQL勉強会 クエリチューニング編

1章 対話的にクエリを作る

ではどうするか。

都市人口の半分が中国瀋陽の人口よりも多い都市

抽出仕様

SELECT Cty1.CountryCode, Cty1.Name, Cty1.Population FROM City Cty1 WHERE Cty1.Population / 2 > ( SELECT Cty2.Population FROM City Cty2 WHERE Cty2.CountryCode = 'CHN' AND Cty2.Name = 'Shenyang' );

SELECT Cty1.CountryCode, Cty1.Name, Cty1.Population FROM City Cty1 WHERE Cty1.Population > ( SELECT Cty2.Population FROM City Cty2 WHERE Cty2.CountryCode = 'CHN' AND Cty2.Name = 'Shenyang' ) * 2;

= 都市人口が中国瀋陽の倍の人口よりも多い都市

抽出の仕様に影響なし

クエリ

Page 26: MySQL勉強会 クエリチューニング編

+----+-------------+-------+------+---------------+-------------+---------+-------+------+-------------+ | id | select_type | table | type | possible_keys | key | key_len | ref | rows | Extra | +----+-------------+-------+------+---------------+-------------+---------+-------+------+-------------+ | 1 | PRIMARY | Cty1 | ALL | NULL | NULL | NULL | NULL | 4051 | Using where | | 2 | SUBQUERY | Cty2 | ref | CountryCode | CountryCode | 3 | const | 363 | Using where | +----+-------------+-------+------+---------------+-------------+---------+-------+------+-------------+

1章 対話的にクエリを作る

修正後の実行計画は…

+----+-------------+-------+-------+---------------+-------------+---------+------+------+-------------+ | id | select_type | table | type | possible_keys | key | key_len | ref | rows | Extra | +----+-------------+-------+-------+---------------+-------------+---------+------+------+-------------+ | 1 | PRIMARY | Cty1 | range | Population | Population | 4 | NULL | 8 | Using where | | 2 | SUBQUERY | Cty2 | ref | CountryCode | CountryCode | 3 | | 363 | Using where | +----+-------------+-------+-------+---------------+-------------+---------+------+------+-------------+

SELECT Cty1.CountryCode, Cty1.Name, Cty1.Population FROM City Cty1 WHERE Cty1.Population > ( SELECT Cty2.Population FROM City Cty2 WHERE Cty2.CountryCode = 'CHN' AND Cty2.Name = 'Shenyang' ) * 2;

クエリ

クエリチューニング前実行計画

索引列の演算処理を、右辺に移すことで INDEXを利用した範囲検索が可能に!

クエリチューニング後実行計画

Page 27: MySQL勉強会 クエリチューニング編

1章 対話的にクエリを作る

仕上げの実行結果確認

+----------+-------------+------------+ | Name | CountryCode | Population | +----------+-------------+------------+ | Shanghai | CHN | 9696300 | | Seoul | KOR | 9981620 | +----------+-------------+------------+

実 行 結 果

Page 28: MySQL勉強会 クエリチューニング編

1章 対話的にクエリを作る

ちなみに・・・

Page 29: MySQL勉強会 クエリチューニング編

1章 対話的にクエリを作る

索引列を指定しても、オプティマイザがINDEXを 使用できないパターンは他にも存在します。

WHERE Index_Column IS NULL

NULL述語を使用

WHERE SUBSTRING(Index_Column, 1, 3) = 'abc'

SQL関数を使用

WHERE Index_Column <> 'abc'

否定形での条件指定

WHERE Index_Column = 'abc' OR Index_Column = 'def'

ORでの条件指定(INへの置き換えで対応可)

WHERE Index_Column LIKE '%abc%'

中間一致、後方一致でのLIKE述語を使用(前方一致は使用可能)

・ ・ ・

Page 30: MySQL勉強会 クエリチューニング編

1章 まとめ

クエリ作成後は、必ず実行計画を取得すること!

Page 31: MySQL勉強会 クエリチューニング編

1章 対話的にクエリを作る

2章 オプティマイザの判断

3章 サブクエリは「データ」の集合

4章 ソートとインデックス

5章 インデックスは万能ではない

6章 相関サブクエリは諸刃の剣

休憩

Page 32: MySQL勉強会 クエリチューニング編

2章 オプティマイザの判断

人口が400000人超えの都市 都市名 都市人口

SELECT Cty.Name, Cty.Population FROM City Cty WHERE Cty.Population > 400000;

抽出条件 抽出項目

クエリ テーブル

City(都市テーブル) +-------------+------------+----------+-----+ | ColumnID | ColumnName | Type | Key | +-------------+------------+----------+-----+ | ID | 都市ID | int(11) | PRI | | Name | 都市名 | char(35) | | | CountryCode | 国コード | char(3) | MUL | | District | 地区 | char(20) | | | Population | 都市人口 | int(11) | MUL | +-------------+------------+----------+-----+

抽出イメージ

Cty +------+------------+-------------+----------------+------------+ | ID | Name | CountryCode | District | Population | +------+------------+-------------+----------------+------------+ | 135 | Canberra | AUS | Capital Region | 322723 | | 1570 | Gifu | JPN | Gifu | 408007 | | 1822 | Ottawa | CAN | Ontario | 335277 | | 2318 | Pyongyang | PRK | Pyongyang-si | 2484000 | | 3209 | Bratislava | SVK | Bratislava | 448292 | | 3831 | Atlanta | USA | Georgia | 416474 | +------+------------+-------------+----------------+------------+

Cty +------+------------+-------------+----------------+------------+ | ID | Name | CountryCode | District | Population | +------+------------+-------------+----------------+------------+ | 135 | Canberra | AUS | Capital Region | 322723 | | 1570 | Gifu | JPN | Gifu | 408007 |○ | 1822 | Ottawa | CAN | Ontario | 335277 | | 2318 | Pyongyang | PRK | Pyongyang-si | 2484000 |○ | 3209 | Bratislava | SVK | Bratislava | 448292 |○ | 3831 | Atlanta | USA | Georgia | 416474 |○ +------+------------+-------------+----------------+------------+

Cty +------+------------+-------------+----------------+------------+ | ID | Name | CountryCode | District | Population | +------+------------+-------------+----------------+------------+ | 135 | Canberra | AUS | Capital Region | 322723 | | 1570 | Gifu | JPN | Gifu | 408007 |○ | 1822 | Ottawa | CAN | Ontario | 335277 | | 2318 | Pyongyang | PRK | Pyongyang-si | 2484000 |○ | 3209 | Bratislava | SVK | Bratislava | 448292 |○ | 3831 | Atlanta | USA | Georgia | 416474 |○ +------+------------+-------------+----------------+------------+

Page 33: MySQL勉強会 クエリチューニング編

2章 オプティマイザの判断

今回はイケる気がする (`・ω ・´)

Page 34: MySQL勉強会 クエリチューニング編

2章 オプティマイザの判断

+----+-------------+-------+------+---------------+------+---------+------+------+-------------+ | id | select_type | table | type | possible_keys | key | key_len | ref | rows | Extra | +----+-------------+-------+------+---------------+------+---------+------+------+-------------+ | 1 | SIMPLE | Cty | ALL | Population | NULL | NULL | NULL | 4051 | Using where | +----+-------------+-------+------+---------------+------+---------+------+------+-------------+

EXPLAIN SELECT Cty.CountryCode, Cty.Name, Cty.Population FROM City Cty WHERE Cty.Population > 400000;

…あれっ!?

実行計画

実行計画は…

Page 35: MySQL勉強会 クエリチューニング編

+----+-------------+-------+------+---------------+------+---------+------+------+-------------+ | id | select_type | table | type | possible_keys | key | key_len | ref | rows | Extra | +----+-------------+-------+------+---------------+------+---------+------+------+-------------+ | 1 | SIMPLE | Cty | ALL | Population | NULL | NULL | NULL | 4051 | Using where | +----+-------------+-------+------+---------------+------+---------+------+------+-------------+

2章 オプティマイザの判断

possible_keysを見てください。

INDEX「Population」は使える状況であることがわかります。

+----+-------------+-------+------+---------------+------+---------+------+------+-------------+ | id | select_type | table | type | possible_keys | key | key_len | ref | rows | Extra | +----+-------------+-------+------+---------------+------+---------+------+------+-------------+ | 1 | SIMPLE | Cty | ALL | Population | NULL | NULL | NULL | 4051 | Using where | +----+-------------+-------+------+---------------+------+---------+------+------+-------------+

+----+-------------+-------+------+---------------+------+---------+------+------+-------------+ | id | select_type | table | type | possible_keys | key | key_len | ref | rows | Extra | +----+-------------+-------+------+---------------+------+---------+------+------+-------------+ | 1 | PRIMARY | Cty1 | ALL | NULL | NULL | NULL | NULL | 4051 | Using where | | 2 | SUBQUERY | Cty2 | ALL | NULL | NULL | NULL | NULL | 4051 | Using where | +----+-------------+-------+------+---------------+------+---------+------+------+-------------+

※1章のチューニング前の実行計画

Page 36: MySQL勉強会 クエリチューニング編

2章 オプティマイザの判断

では、なぜ?

Page 37: MySQL勉強会 クエリチューニング編

2章 オプティマイザの判断

オプティマイザがINDEXを使用するよりも、 テーブルスキャンをした方が効率が良いと判断したのです。

※取得するデータの量が表全体の5%~15%以下(目安)の場合にインデックスを使用

Page 38: MySQL勉強会 クエリチューニング編

2章 オプティマイザの判断

試しに人口600000人超え(表全体の約10%)の都市を抽出してみます。

+----+-------------+-------+-------+---------------+------------+---------+------+------+-------------+ | id | select_type | table | type | possible_keys | key | key_len | ref | rows | Extra | +----+-------------+-------+-------+---------------+------------+---------+------+------+-------------+ | 1 | SIMPLE | Cty | range | Population | Population | 4 | NULL | 428 | Using where | +----+-------------+-------+-------+---------------+------------+---------+------+------+-------------+

EXPLAIN SELECT Cty.CountryCode, Cty.Name, Cty.Population FROM City Cty WHERE Cty.Population > 600000;

この条件の場合は、INDEXを利用した方が効率が良いと判断したようです。

実行計画

Page 39: MySQL勉強会 クエリチューニング編

2章 まとめ

INDEXを使えないのか、使わないのか。 適切に判断し、適切にアプローチを。

Page 40: MySQL勉強会 クエリチューニング編

1章 対話的にクエリを作る

2章 オプティマイザの判断

3章 サブクエリは「データ」の集合

4章 ソートとインデックス

5章 インデックスは万能ではない

6章 相関サブクエリは諸刃の剣

休憩

Page 41: MySQL勉強会 クエリチューニング編

3章 サブクエリは「データ」の集合

SELECT Ctr.Name CountryName, Sub.Name CityName, Sub.Population FROM Country Ctr LEFT JOIN ( SELECT Cty.CountryCode, Cty.Name, Cty.Population FROM City Cty WHERE Cty.Population > 400000 ) Sub ON Ctr.Code = Sub.CountryCode;

City(都市テーブル) +-------------+------------+-----+ | ColumnID | ColumnName | Key | +-------------+------------+-----+ | ID | 都市ID | PRI | | Name | 都市名 | | | CountryCode | 国コード | MUL | | District | 地区 | | | Population | 都市人口 | MUL | +-------------+------------+-----+

Country(国テーブル) +----------+---------------+-----+ | ColumnID | ColumnName | Key | +----------+---------------+-----+ | Code | 国コード | PRI | | Name | 国名 | | ==============省略================

全ての国名の一覧を表示。 各国に人口が400000人以上の都市が存在する場合、 その各都市の名称と人口の情報を付与する。 存在しない場合はNULLを表示する。

国名 都市名 都市人口

抽出条件 抽出項目

クエリ テーブル

抽出イメージ Ctr +------+---------------+ | Code | Name | +------+---------------+ | JPN | Japan | | KOR | South Korea | | SVK | Slovakia | | USA | United States | +------+---------------+

Cty +------------+-------------+------------+ | Name | CountryCode | Population | +------------+-------------+------------+ | Gifu | JPN | 408007 | | Pyongyang | PRK | 2484000 | | Bratislava | SVK | 448292 | | Atlanta | USA | 416474 | +------------+-------------+------------+

Ctr LEFT JOIN Sub +---------------+------------+------------+ | CountryName | CityName | Population | +---------------+------------+------------+ | Japan | Gifu | 408007 | | South Korea | NULL | NULL | | Slovakia | Bratislava | 448292 | | United States | Atlanta | 416474 | +---------------+------------+------------+

Sub +------------+-------------+------------+ | Name | CountryCode | Population | +------------+-------------+------------+ | Gifu | JPN | 408007 |○ | Pyongyang | PRK | 2484000 | | Bratislava | SVK | 448292 |○ | Atlanta | USA | 416474 |○ +------------+-------------+------------+

+ =

Page 42: MySQL勉強会 クエリチューニング編

3章 サブクエリは「データ」の集合

クエリの実行計画を確認してみます。

+----+-------------+------------+------+---------------+------+---------+------+------+-------------+ | id | select_type | table | type | possible_keys | key | key_len | ref | rows | Extra | +----+-------------+------------+------+---------------+------+---------+------+------+-------------+ | 1 | PRIMARY | Ctr | ALL | NULL | NULL | NULL | NULL | 241 | | | 1 | PRIMARY | <derived2> | ALL | NULL | NULL | NULL | NULL | 737 | | | 2 | DERIVED | Cty | ALL | Population | NULL | NULL | NULL | 4051 | Using where | +----+-------------+------------+------+---------------+------+---------+------+------+-------------+

EXPLAIN SELECT Ctr.Name CountryName, Sub.Name CityName, Sub.Population FROM Country Ctr LEFT JOIN ( SELECT Cty.CountryCode, Cty.Name, Cty.Population FROM City Cty WHERE Cty.Population > 400000 ) Sub ON Ctr.Code = Sub.CountryCode;

+----+-------------+------------+------+---------------+------+---------+------+------+-------------+ | id | select_type | table | type | possible_keys | key | key_len | ref | rows | Extra | +----+-------------+------------+------+---------------+------+---------+------+------+-------------+ | 1 | PRIMARY | Ctr | ALL | NULL | NULL | NULL | NULL | 241 | | | 1 | PRIMARY | <derived2> | ALL | NULL | NULL | NULL | NULL | 737 | | | 2 | DERIVED | Cty | ALL | Population | NULL | NULL | NULL | 4051 | Using where | +----+-------------+------------+------+---------------+------+---------+------+------+-------------+

結合キーとして、 INDEXが使われていません。

実行計画

Page 43: MySQL勉強会 クエリチューニング編

derived2

3章 サブクエリは「データ」の集合

図解するとこんな感じです。

+----+-------------+------------+------+---------------+------+---------+------+------+-------------+ | id | select_type | table | type | possible_keys | key | key_len | ref | rows | Extra | +----+-------------+------------+------+---------------+------+---------+------+------+-------------+ | 1 | PRIMARY | Ctr | ALL | NULL | NULL | NULL | NULL | 241 | | | 1 | PRIMARY | <derived2> | ALL | NULL | NULL | NULL | NULL | 737 | | | 2 | DERIVED | Cty | ALL | Population | NULL | NULL | NULL | 4051 | Using where | +----+-------------+------------+------+---------------+------+---------+------+------+-------------+

Cty

PK

INDEX

Ctr

サブクエリにて抽出した集合には PKやINDEXの情報は含まれない。

Ctrを基準にderived2とJOIN

PK

INDEX

※基準になるテーブルのINDEXは使われません。

Page 44: MySQL勉強会 クエリチューニング編

3章 サブクエリは「データ」の集合

ちょっと、振り返ります。

Page 45: MySQL勉強会 クエリチューニング編

3章 サブクエリは「データ」の集合

SELECT Ctr.Name CountryName, Sub.Name CityName, Sub.Population FROM Country Ctr LEFT JOIN ( SELECT Cty.CountryCode, Cty.Name, Cty.Population FROM City Cty WHERE Cty.Population > 400000 ) Sub ON Ctr.Code = Sub.CountryCode;

City(都市テーブル) +-------------+------------+-----+ | ColumnID | ColumnName | Key | +-------------+------------+-----+ | ID | 都市ID | PRI | | Name | 都市名 | | | CountryCode | 国コード | MUL | | District | 地区 | | | Population | 都市人口 | MUL | +-------------+------------+-----+

Country(国テーブル) +----------+---------------+-----+ | ColumnID | ColumnName | Key | +----------+---------------+-----+ | Code | 国コード | PRI | | Name | 国名 | | ==============省略================

クエリ テーブル

抽出イメージ Ctr +------+---------------+ | Code | Name | +------+---------------+ | JPN | Japan | | KOR | South Korea | | SVK | Slovakia | | USA | United States | +------+---------------+

Cty +------------+-------------+------------+ | Name | CountryCode | Population | +------------+-------------+------------+ | Gifu | JPN | 408007 | | Pyongyang | PRK | 2484000 | | Bratislava | SVK | 448292 | | Atlanta | USA | 416474 | +------------+-------------+------------+

Ctr LEFT JOIN Sub +---------------+------------+------------+ | CountryName | CityName | Population | +---------------+------------+------------+ | Japan | Gifu | 408007 | | South Korea | NULL | NULL | | Slovakia | Bratislava | 448292 | | United States | Atlanta | 416474 | +---------------+------------+------------+

Sub +------------+-------------+------------+ | Name | CountryCode | Population | +------------+-------------+------------+ | Gifu | JPN | 408007 |○ | Pyongyang | PRK | 2484000 | | Bratislava | SVK | 448292 |○ | Atlanta | USA | 416474 |○ +------------+-------------+------------+

+ =

Countryテーブルと結合するのはCityテーブルではなく、 あくまでもSubというデータの集合(テンポラリテーブル)

Page 46: MySQL勉強会 クエリチューニング編

3章 サブクエリは「データ」の集合

ではどうするか。

SELECT Ctr.Name CountryName, Sub.Name CityName, Sub.Population FROM Country Ctr LEFT JOIN (SELECT Cty.CountryCode, Cty.Name, Cty.Population FROM City Cty WHERE Cty.Population > 400000 ) Sub ON Ctr.Code = Sub.CountryCode;

SELECT Ctr.Name CountryName, Cty.Name CityName, Cty.Population FROM Country Ctr LEFT JOIN City Cty ON Ctr.Code = Cty.CountryCode;

クエリ SELECT Ctr.Name CountryName, Cty.Name CityName, Cty.Population FROM Country Ctr LEFT JOIN City Cty ON Ctr.Code = Cty.CountryCode AND Cty.Population > 400000;

Ctr +------+---------------+ | Code | Name | +------+---------------+ | JPN | Japan | | KOR | South Korea | | SVK | Slovakia | | USA | United States | +------+---------------+

Cty +------------+-------------+------------+ | Name | CountryCode | Population | +------------+-------------+------------+ | Gifu | JPN | 408007 | | Kwangmyong | KOR | 350914 | | Pyongyang | PRK | 2484000 | | Bratislava | SVK | 448292 | | Atlanta | USA | 416474 | +------------+-------------+------------+

Ctr LEFT JOIN Cty +---------------+------------+------------+ | CountryName | CityName | Population | +---------------+------------+------------+ | Japan | Gifu | 408007 | | South Korea | Kwangmyong | 350914 | | Slovakia | Bratislava | 448292 | | United States | Atlanta | 416474 | +---------------+------------+------------+

+ =

抽出イメージ

City(都市テーブル) +-------------+------------+-----+ | ColumnID | ColumnName | Key | +-------------+------------+-----+ | ID | 都市ID | PRI | | Name | 都市名 | | | CountryCode | 国コード | MUL | | District | 地区 | | | Population | 都市人口 | MUL | +-------------+------------+-----+

Country(国テーブル) +----------+---------------+-----+ | ColumnID | ColumnName | Key | +----------+---------------+-----+ | Code | 国コード | PRI | | Name | 国名 | | ==============省略================

テーブル

Cty +------------+-------------+------------+ | Name | CountryCode | Population | +------------+-------------+------------+ | Gifu | JPN | 408007 | | Kwangmyong | KOR | 350914 | | Pyongyang | PRK | 2484000 | | Bratislava | SVK | 448292 | | Atlanta | USA | 416474 | +------------+-------------+------------+

Ctr LEFT JOIN Cty +---------------+------------+------------+ | CountryName | CityName | Population | +---------------+------------+------------+ | Japan | Gifu | 408007 | | South Korea | NULL | NULL | | Slovakia | Bratislava | 448292 | | United States | Atlanta | 416474 | +---------------+------------+------------+

Page 47: MySQL勉強会 クエリチューニング編

+----+-------------+------------+------+---------------+------+---------+------+------+-------------+ | id | select_type | table | type | possible_keys | key | key_len | ref | rows | Extra | +----+-------------+------------+------+---------------+------+---------+------+------+-------------+ | 1 | PRIMARY | Ctr | ALL | NULL | NULL | NULL | NULL | 241 | | | 1 | PRIMARY | <derived2> | ALL | NULL | NULL | NULL | NULL | 737 | | | 2 | DERIVED | Cty | ALL | Population | NULL | NULL | NULL | 4051 | Using where | +----+-------------+------------+------+---------------+------+---------+------+------+-------------+

3章 サブクエリは「データ」の集合

修正後の実行計画は…

+----+-------------+-------+------+------------------------+-------------+---------+----------------+------+-------+ | id | select_type | table | type | possible_keys | key | key_len | ref | rows | Extra | +----+-------------+-------+------+------------------------+-------------+---------+----------------+------+-------+ | 1 | SIMPLE | Ctr | ALL | NULL | NULL | NULL | NULL | 241 | | | 1 | SIMPLE | Cty | ref | CountryCode,Population | CountryCode | 3 | world.Ctr.Code | 7 | | +----+-------------+-------+------+------------------------+-------------+---------+----------------+------+-------+

EXPLAIN SELECT Ctr.Name CountryName, Cty.Name CityName, Cty.Population FROM Country Ctr LEFT JOIN City Cty ON Ctr.Code = Cty.CountryCode AND Cty.Population > 400000;

クエリ

サブクエリを使わず直接テーブル同士をJOINしたことで、 INDEXを利用したJOINが可能に!

クエリチューニング前実行計画 クエリチューニング後実行計画

Page 48: MySQL勉強会 クエリチューニング編

3章 サブクエリは「データ」の集合

ちなみに・・・

Page 49: MySQL勉強会 クエリチューニング編

3章 サブクエリは「データ」の集合

チューニング前のクエリの結合方式を内部結合にすると…

+----+-------------+------------+------+---------------+------+---------+------+------+-------------+ | id | select_type | table | type | possible_keys | key | key_len | ref | rows | Extra | +----+-------------+------------+------+---------------+------+---------+------+------+-------------+ | 1 | PRIMARY | Ctr | ALL | NULL | NULL | NULL | NULL | 241 | | | 1 | PRIMARY | <derived2> | ALL | NULL | NULL | NULL | NULL | 737 | | | 2 | DERIVED | Cty | ALL | Population | NULL | NULL | NULL | 4051 | Using where | +----+-------------+------------+------+---------------+------+---------+------+------+-------------+

EXPLAIN SELECT Ctr.Name CountryName, Sub.Name CityName, Sub.Population FROM Country Ctr LEFT JOIN ( SELECT Cty.CountryCode, Cty.Name, Cty.Population FROM City Cty WHERE Cty.Population > 400000 ) Sub ON Ctr.Code = Sub.CountryCode;

EXPLAIN SELECT Ctr.Name CountryName, Sub.Name CityName, Sub.Population FROM Country Ctr INNER JOIN ( SELECT Cty.CountryCode, Cty.Name, Cty.Population FROM City Cty WHERE Cty.Population > 400000 ) Sub ON Ctr.Code = Sub.CountryCode;

+----+-------------+------------+--------+---------------+---------+---------+-----------------+------+-------------+ | id | select_type | table | type | possible_keys | key | key_len | ref | rows | Extra | +----+-------------+------------+--------+---------------+---------+---------+-----------------+------+-------------+ | 1 | PRIMARY | <derived2> | ALL | NULL | NULL | NULL | NULL | 737 | | | 1 | PRIMARY | Ctr | eq_ref | PRIMARY | PRIMARY | 3 | Sub.CountryCode | 1 | | | 2 | DERIVED | Cty | ALL | Population | NULL | NULL | NULL | 4051 | Using where | +----+-------------+------------+--------+---------------+---------+---------+-----------------+------+-------------+

INDEXが… 使われている!?

実行計画

Page 50: MySQL勉強会 クエリチューニング編

3章 サブクエリは「データ」の集合

図で説明します

+----+-------------+------------+--------+---------------+---------+---------+-----------------+------+-------------+ | id | select_type | table | type | possible_keys | key | key_len | ref | rows | Extra | +----+-------------+------------+--------+---------------+---------+---------+-----------------+------+-------------+ | 1 | PRIMARY | <derived2> | ALL | NULL | NULL | NULL | NULL | 737 | | | 1 | PRIMARY | Ctr | eq_ref | PRIMARY | PRIMARY | 3 | Sub.CountryCode | 1 | | | 2 | DERIVED | Cty | ALL | Population | NULL | NULL | NULL | 4051 | Using where | +----+-------------+------------+--------+---------------+---------+---------+-----------------+------+-------------+

Cty

PK

INDEX

derived2 Ctr

derived2を基準にCtrとJOIN

内部結合の場合、どちらを基準にしても良いため、 オプティマイザが効率的と判断した集合を基準に結合する。

サブクエリにて抽出した集合には PKやINDEXの情報は含まれていない。

PK PK

INDEX

Page 51: MySQL勉強会 クエリチューニング編

3章 まとめ

サブクエリで抽出した集合にはINDEXは無い! サブクエリを結合する場合は、 サブクエリ内で十分にデータを絞り込むか、 別の抽出方法を検討すること。

Page 52: MySQL勉強会 クエリチューニング編

1章 対話的にクエリを作る

2章 オプティマイザの判断

3章 サブクエリは「データ」の集合

4章 ソートとインデックス

5章 インデックスは万能ではない

6章 相関サブクエリは諸刃の剣

休憩

Page 53: MySQL勉強会 クエリチューニング編

1章 対話的にクエリを作る

2章 オプティマイザの判断

3章 サブクエリは「データ」の集合

4章 ソートとインデックス

5章 インデックスは万能ではない

6章 相関サブクエリは諸刃の剣

休憩

Page 54: MySQL勉強会 クエリチューニング編

4章 ソートとインデックス

全ての国名とその国が所有する都市数

クエリ

SELECT Ctr.Code CountryCode, Ctr.Name CountryName, COUNT(Cty.ID) CityCount FROM Country Ctr LEFT JOIN City Cty ON Ctr.Code = Cty.CountryCode GROUP BY Ctr.Code, Ctr.Name;

抽出仕様 抽出項目 国コード 国名 都市数

City(都市テーブル) +-------------+------------+-----+ | ColumnID | ColumnName | Key | +-------------+------------+-----+ | ID | 都市ID | PRI | | Name | 都市名 | | | CountryCode | 国コード | MUL | | District | 地区 | | | Population | 都市人口 | MUL | +-------------+------------+-----+

Country(国テーブル) +----------+---------------+-----+ | ColumnID | ColumnName | Key | +----------+---------------+-----+ | Code | 国コード | PRI | | Name | 国名 | | ==============省略================

テーブル

抽出イメージ

Ctr +------+---------------+ | Code | Name | +------+---------------+ | ATA | Antarctica | | JPN | Japan | | KOR | South Korea | | USA | United States | +------+---------------+

Cty +------+----------+-------------+ | ID | Name | CountryCode | +------+----------+-------------+ | 129 | Aruba | ABW | | 1532 | Tokyo | JPN | | 1534 | Osaka | JPN | | 2331 | Seoul | KOR | | 3793 | New York | USA | +------+----------+-------------+

Ctr LEFT JOIN Cty +------+---------------+------+----------+-------------+ | Code | Name | ID | Name | CountryCode | +------+---------------+------+----------+-------------+ | ATA | Antarctica | NULL | NULL | NULL | | JPN | Japan | 1532 | Tokyo | JPN | | JPN | Japan | 1534 | Osaka | JPN | | KOR | South Korea | 2331 | Seoul | KOR | | USA | United States | 3793 | New York | USA | +------+---------------+------+----------+-------------+

+ =

Ctr LEFT JOIN Cty GROUP BY Ctr.Column +-------------+---------------+-----------+ | CountryCode | CountryName | CityCount | +-------------+---------------+-----------+ | ATA | Antarctica | 0 | | JPN | Japan | 2 | | KOR | South Korea | 1 | | USA | United States | 1 | +-------------+---------------+-----------+

Cty +------+----------+-------------+ | ID | Name | CountryCode | +------+----------+-------------+ | 129 | Aruba | ABW | | 1532 | Tokyo | JPN |○ | 1534 | Osaka | JPN |○ | 2331 | Seoul | KOR |○ | 3793 | New York | USA |○ +------+----------+-------------+

Page 55: MySQL勉強会 クエリチューニング編

4章 ソートとインデックス

SELECT Ctr.Code CountryCode, Ctr.Name CountryName, COUNT(Cty.ID) CityCount FROM Country Ctr LEFT JOIN City Cty ON Ctr.Code = Cty.CountryCode GROUP BY Ctr.Code, Ctr.Name;

+----+-------------+-------+------+---------------+-------------+---------+--------------------+------+---------------------------------+ | id | select_type | table | type | possible_keys | key | key_len | ref | rows | Extra | +----+-------------+-------+------+---------------+-------------+---------+--------------------+------+---------------------------------+ | 1 | SIMPLE | Ctr | ALL | NULL | NULL | NULL | NULL | 205 | Using temporary; Using filesort | | 1 | SIMPLE | Cty | ref | CountryCode | CountryCode | 3 | world_big.Ctr.Code | 873 | Using index | +----+-------------+-------+------+---------------+-------------+---------+--------------------+------+---------------------------------+

テーブルの結合キーに関してはINDEXが使われていますが、

今回注目して頂きたいのは結合基準となるテーブルのExtraフィールドです。

クエリの実行計画を確認してみます。

実行計画

+----+-------------+-------+------+---------------+-------------+---------+--------------------+------+---------------------------------+ | id | select_type | table | type | possible_keys | key | key_len | ref | rows | Extra | +----+-------------+-------+------+---------------+-------------+---------+--------------------+------+---------------------------------+ | 1 | SIMPLE | Ctr | ALL | NULL | NULL | NULL | NULL | 205 | Using temporary; Using filesort | | 1 | SIMPLE | Cty | ref | CountryCode | CountryCode | 3 | world_big.Ctr.Code | 873 | Using index | +----+-------------+-------+------+---------------+-------------+---------+--------------------+------+---------------------------------+

+----+-------------+-------+------+---------------+-------------+---------+--------------------+------+---------------------------------+ | id | select_type | table | type | possible_keys | key | key_len | ref | rows | Extra | +----+-------------+-------+------+---------------+-------------+---------+--------------------+------+---------------------------------+ | 1 | SIMPLE | Ctr | ALL | NULL | NULL | NULL | NULL | 205 | Using temporary; Using filesort | | 1 | SIMPLE | Cty | ref | CountryCode | CountryCode | 3 | world_big.Ctr.Code | 873 | Using index | +----+-------------+-------+------+---------------+-------------+---------+--------------------+------+---------------------------------+

Page 56: MySQL勉強会 クエリチューニング編

4章 ソートとインデックス

図解するとこんな感じ。

Ctr

Ctr1

Ctr2

Ctr3

Ctr4

Cty

Cty1-1

Cty3-1

Cty2-2

Cty2-1

Cty4-1

テンポラリテーブル

LEFT JOIN

SORT

出力

Ctr1

Ctr2

Ctr3

Ctr4

Cty2-1

Cty2-2

Cty3-1

Cty4-1

Ctr1

Ctr2

Ctr3

Ctr4

Cty3-1

Cty2-2

Cty2-1

Cty4-1

Ctr +------+---------------+ | Code | Name | +------+---------------+ | USA | United States |4 | JPN | Japan |2 | ATA | Antarctica |1 | KOR | South Korea |3 +------+---------------+

Cty +----------+-------------+ | Name | CountryCode | +----------+-------------+ | Osaka | JPN |2-2 | Seoul | KOR |3-1 | Aruba | ABW |1-1 | New York | USA |4-1 | Tokyo | JPN |2-1 +----------+-------------+

Ctr LEFT JOIN Cty GROUP BY Ctr.Columns +-------------+---------------+-----------+ | CountryCode | CountryName | CityCount | +-------------+---------------+-----------+ | ATA | Antarctica | 0 | | JPN | Japan | 2 | | KOR | South Korea | 1 | | USA | United States | 1 | +-------------+---------------+-----------+

テンポラリテーブル

Using temporary Using filesort

Page 57: MySQL勉強会 クエリチューニング編

4章 ソートとインデックス

Ctr

Ctr1

Ctr2

Ctr3

Ctr4

Cty

Cty1-1

Cty3-1

Cty2-2

Cty2-1

Cty4-1

テンポラリテーブル

LEFT JOIN

SORT

出力

Ctr1

Ctr2

Ctr3

Ctr4

Cty2-1

Cty2-2

Cty3-1

Cty4-1

Ctr1

Ctr2

Ctr3

Ctr4

Cty3-1

Cty2-2

Cty2-1

Cty4-1

Ctr +------+---------------+ | Code | Name | +------+---------------+ | USA | United States |4 | JPN | Japan |2 | ATA | Antarctica |1 | KOR | South Korea |3 +------+---------------+

Cty +----------+-------------+ | Name | CountryCode | +----------+-------------+ | Tokyo | JPN |2-2 | Seoul | KOR |3-1 | Aruba | ABW |1-1 | New York | USA |4-1 | Osaka | JPN |2-1 +----------+-------------+

Ctr LEFT JOIN Cty GROUP BY Ctr_Column +-------------+---------------+-----------+ | CountryCode | CountryName | CityCount | +-------------+---------------+-----------+ | ATA | Antarctica | 0 | | JPN | Japan | 2 | | KOR | South Korea | 1 | | USA | United States | 1 | +-------------+---------------+-----------+

テンポラリテーブル

Using temporary Using filesort

実はこいつら、やっつけられます。

Page 58: MySQL勉強会 クエリチューニング編

4章 ソートとインデックス

対応方法の一つとして複合INDEXを用いる方法があります。

SELECT Ctr.Code CountryCode, Ctr.Name CountryName, COUNT(Cty.ID) CityCount FROM Country Ctr LEFT JOIN City Cty ON Ctr.Code = Cty.CountryCode GROUP BY Ctr.Code, Ctr.Name;

上記クエリのGROUP BY句にて使用する集計キーに対して、 下記のコマンドで複合INDEXを貼ります。

ALTER TABLE Country ADD INDEX mul_idx1(Code, Name);

Page 59: MySQL勉強会 クエリチューニング編

4章 ソートとインデックス

複合INDEX対応後のクエリの実行計画を確認してみます。

SELECT Ctr.Code CountryCode, Ctr.Name CountryName, COUNT(Cty.ID) CityCount FROM Country Ctr LEFT JOIN City Cty ON Ctr.Code = Cty.CountryCode GROUP BY Ctr.Code, Ctr.Name;

+----+-------------+-------+-------+---------------+-------------+---------+--------------------+------+-------------+ | id | select_type | table | type | possible_keys | key | key_len | ref | rows | Extra | +----+-------------+-------+-------+---------------+-------------+---------+--------------------+------+-------------+ | 1 | SIMPLE | Ctr | index | NULL | mul_idx1 | 55 | NULL | 1 | Using index | | 1 | SIMPLE | Cty | ref | CountryCode | CountryCode | 3 | world_big.Ctr.Code | 873 | Using index | +----+-------------+-------+-------+---------------+-------------+---------+--------------------+------+-------------+

+---------+----------+--------------+-------------+ | Table | Key_name | Seq_in_index | Column_name | +---------+----------+--------------+-------------+ | Country | PRIMARY | 1 | Code | | Country | mul_idx1 | 1 | Code | | Country | mul_idx1 | 2 | Name | +---------+----------+--------------+-------------+

実行計画

複合INDEXを貼ることによって、 INDEXを用いたソートが可能に!

Page 60: MySQL勉強会 クエリチューニング編

4章 ソートとインデックス

そもそも、ソートにINDEXを使うってどういうこと?

Page 61: MySQL勉強会 クエリチューニング編

4章 ソートとインデックス

mul_idx1(Code, Name)のINDEX情報

head

AAA CCC

33 3 333

BBB

22 2 222 11 1 111

Code

Name

① ② ③ ④ ⑤ ⑥ ⑦ ⑧ ⑨

複合INDEXは下記図のようなINDEX情報となります。

INDEXとして保持している情報はCode、Nameの順にソートされています。

つまりINDEX順に行を取り出したその時、既にソートは終わっているのです。

Page 62: MySQL勉強会 クエリチューニング編

4章 ソートとインデックス

Ctr

Ctr1

Ctr2

Ctr3

Ctr4

Cty

Cty1-1

Cty3-1

Cty2-2

Cty2-1

Cty4-1

LEFT JOIN

SORT

出力

Ctr1

Ctr2

Ctr3

Ctr4

Cty2-1

Cty2-2

Cty3-1

Cty4-1

Ctr1

Ctr2

Ctr3

Ctr4

Cty3-1

Cty2-2

Cty2-1

Cty4-1

Using temporary Using filesort

つまりこういうこと。

INDEX情報

データ抽出

Ctr

LEFT JOIN

Page 63: MySQL勉強会 クエリチューニング編

4章 ソートとインデックス

Codeのみの単一INDEXではだめなの?

Page 64: MySQL勉強会 クエリチューニング編

4章 ソートとインデックス

CodeのみのINDEX情報

head

AAA CCC BBB Code

単一INDEXは下記図のようなINDEX情報となります。

② ③

上の図だけでダメなのはわかりますね。

インデックス順にデータを取得しても、Name列はソートされていない状態です。

Page 65: MySQL勉強会 クエリチューニング編

4章 ソートとインデックス

複合INDEXの定義順を逆にするとどうなるの?

Page 66: MySQL勉強会 クエリチューニング編

4章 ソートとインデックス

mul_idx1(Name, Code)のINDEX情報

head

1 3

CCD CCC CCE

2

BBC BBB BBD AAB AAA AAC

Name

Code

① ② ③ ④ ⑤ ⑥ ⑦ ⑧ ⑨

複合INDEXの定義順を逆にしてみます。

SELECT Ctr.Code CountryCode, Ctr.Name CountryName, COUNT(Cty.ID) CityCount FROM Country Ctr LEFT JOIN City Cty ON Ctr.Code = Cty.CountryCode GROUP BY Ctr.Code, Ctr.Name;

クエリ

INDEX情報はName、Codeの順でソートされています。

よって、INDEX順でデータを取得したとしても、

Code、Name順にソートする必要が出てきます。

Page 67: MySQL勉強会 クエリチューニング編

4章 ソートとインデックス

他にも色々あるので、この情報を基にいろいろ調べてみてください。

Page 68: MySQL勉強会 クエリチューニング編

4章 ソートとインデックス

68

実は・・・

Page 69: MySQL勉強会 クエリチューニング編

4章 ソートとインデックス

当件、新たにINDEXを付けなくてもINDEX付与後以上の パフォーマンスにチューニングすることが可能です。 次章で説明します。

Page 70: MySQL勉強会 クエリチューニング編

4章 まとめ

GROUP BY、ORDER BYでも インデックスの利用を意識すること!

Page 71: MySQL勉強会 クエリチューニング編

1章 対話的にクエリを作る

2章 オプティマイザの判断

3章 サブクエリは「データ」の集合

4章 ソートとインデックス

5章 インデックスは万能ではない

6章 相関サブクエリは諸刃の剣

休憩

Page 72: MySQL勉強会 クエリチューニング編

5章 インデックスは万能ではない

4章で例に挙げたクエリの実際の実行時間は以下です。

+----+-------------+-------+------+---------------+-------------+---------+--------------------+------+---------------------------------+ | id | select_type | table | type | possible_keys | key | key_len | ref | rows | Extra | +----+-------------+-------+------+---------------+-------------+---------+--------------------+------+---------------------------------+ | 1 | SIMPLE | Ctr | ALL | NULL | NULL | NULL | NULL | 205 | Using temporary; Using filesort | | 1 | SIMPLE | Cty | ref | CountryCode | CountryCode | 3 | world_big.Ctr.Code | 873 | Using index | +----+-------------+-------+------+---------------+-------------+---------+--------------------+------+---------------------------------+

複合INDEX付与前

複合INDEX付与後

+----+-------------+-------+-------+---------------+-------------+---------+--------------------+------+-------------+ | id | select_type | table | type | possible_keys | key | key_len | ref | rows | Extra | +----+-------------+-------+-------+---------------+-------------+---------+--------------------+------+-------------+ | 1 | SIMPLE | Ctr | index | NULL | mul_idx1 | 55 | NULL | 1 | Using index | | 1 | SIMPLE | Cty | ref | CountryCode | CountryCode | 3 | world_big.Ctr.Code | 873 | Using index | +----+-------------+-------+-------+---------------+-------------+---------+--------------------+------+-------------+

実行時間

239 rows in set (0.26 sec)

実行時間

239 rows in set (0.59 sec)

※データ量増

Page 73: MySQL勉強会 クエリチューニング編

5章 インデックスは万能ではない

クエリ

抽出仕様 抽出項目 国コード 国名 都市数

City(都市テーブル) (件数増幅:411979)

+-------------+------------+-----+ | ColumnID | ColumnName | Key | +-------------+------------+-----+ | ID | 都市ID | PRI | | Name | 都市名 | | | CountryCode | 国コード | MUL | | District | 地区 | | | Population | 都市人口 | MUL | +-------------+------------+-----+

Country(国テーブル) +----------+---------------+-----+ | ColumnID | ColumnName | Key | +----------+---------------+-----+ | Code | 国コード | PRI | | Name | 国名 | | ==============省略================

テーブル

抽出イメージ

Ctr +------+---------------+ | Code | Name | +------+---------------+ | ATA | Antarctica | | JPN | Japan | | KOR | South Korea | | USA | United States | +------+---------------+

Cty +-------------+-----------+ | CountryCode | CityCount | +-------------+-----------+ | ABW | 2 | | JPN | 2 | | KOR | 1 | | USA | 1 | +-------------+-----------+

Ctr LEFT JOIN Sub GROUP BY Ctr_Column +-------------+---------------+-----------+ | CountryCode | CountryName | CityCount | +-------------+---------------+-----------+ | ATA | Antarctica | 0 | | JPN | Japan | 2 | | KOR | South Korea | 1 | | USA | United States | 1 | +-------------+---------------+-----------+

+ =

SELECT Ctr.Code CountryCode, Ctr.Name CountryName, IFNULL(Sub.CityCount, 0) CityCount FROM Country Ctr LEFT JOIN ( SELECT Cty.CountryCode, COUNT(*) CityCount FROM City Cty GROUP BY Cty.CountryCode ) Sub ON Ctr.Code = Sub.CountryCode;

Sub +-------------+-----------+ | CountryCode | CityCount | +-------------+-----------+ | ABW | 2 | | JPN | 2 |○ | KOR | 1 |○ | USA | 1 |○ +-------------+-----------+

全ての国名とその国が所有する都市数

Page 74: MySQL勉強会 クエリチューニング編

5章 インデックスは万能ではない

クエリの実行計画を確認してみます。

SELECT Ctr.Code CountryCode, Ctr.Name CountryName, IFNULL(Sub.CityCount, 0) CityCount FROM Country Ctr LEFT JOIN ( SELECT Cty.CountryCode, COUNT(*) CityCount FROM City Cty GROUP BY Cty.CountryCode ) Sub ON Ctr.Code = Sub.CountryCode;

+----+-------------+------------+-------+---------------+-------------+---------+------+--------+-------------+ | id | select_type | table | type | possible_keys | key | key_len | ref | rows | Extra | +----+-------------+------------+-------+---------------+-------------+---------+------+--------+-------------+ | 1 | PRIMARY | Ctr | ALL | NULL | NULL | NULL | NULL | 245 | | | 1 | PRIMARY | <derived2> | ALL | NULL | NULL | NULL | NULL | 232 | | | 2 | DERIVED | Cty | index | NULL | CountryCode | 3 | NULL | 412116 | Using index | +----+-------------+------------+-------+---------------+-------------+---------+------+--------+-------------+

サブクエリのGROUP BYにて単一INDEXを集計キーとして使用! ※JOINがテーブルスキャンとなっているが負荷は軽微!

実行計画

Page 75: MySQL勉強会 クエリチューニング編

5章 インデックスは万能ではない

複合インデックス付与版

クエリ作り直し版

+----+-------------+------------+-------+---------------+-------------+---------+------+--------+-------------+ | id | select_type | table | type | possible_keys | key | key_len | ref | rows | Extra | +----+-------------+------------+-------+---------------+-------------+---------+------+--------+-------------+ | 1 | PRIMARY | Ctr | ALL | NULL | NULL | NULL | NULL | 245 | | | 1 | PRIMARY | <derived2> | ALL | NULL | NULL | NULL | NULL | 232 | | | 2 | DERIVED | Cty | index | NULL | CountryCode | 3 | NULL | 412116 | Using index | +----+-------------+------------+-------+---------------+-------------+---------+------+--------+-------------+

実行時間

239 rows in set (0.16 sec)

実行時間

+----+-------------+-------+-------+---------------+-------------+---------+--------------------+------+-------------+ | id | select_type | table | type | possible_keys | key | key_len | ref | rows | Extra | +----+-------------+-------+-------+---------------+-------------+---------+--------------------+------+-------------+ | 1 | SIMPLE | Ctr | index | NULL | mul_idx1 | 55 | NULL | 1 | Using index | | 1 | SIMPLE | Cty | ref | CountryCode | CountryCode | 3 | world_big.Ctr.Code | 873 | Using index | +----+-------------+-------+-------+---------------+-------------+---------+--------------------+------+-------------+

239 rows in set (0.26 sec)

Page 76: MySQL勉強会 クエリチューニング編

5章 まとめ

安易にINDEXを貼る前に、 様々なチューニングパターンを検討すること!

Page 77: MySQL勉強会 クエリチューニング編

1章 対話的にクエリを作る

2章 オプティマイザの判断

3章 サブクエリは「データ」の集合

4章 ソートとインデックス

5章 インデックスは万能ではない

6章 相関サブクエリは諸刃の剣

休憩

Page 78: MySQL勉強会 クエリチューニング編

6章 相関サブクエリは諸刃の剣

各国の最大人口を誇る都市を抽出

クエリ

SELECT Cty1.CountryCode, Cty1.Name, Cty1.Population FROM City Cty1 WHERE Cty1.Population = ( SELECT MAX(Cty2.Population) FROM City Cty2 WHERE Cty2.CountryCode = Cty1.CountryCode );

City(都市テーブル) (件数増幅:411979)

+-------------+------------+-----+ | ColumnID | ColumnName | Key | +-------------+------------+-----+ | ID | 都市ID | PRI | | Name | 都市名 | | | CountryCode | 国コード | MUL | | District | 地区 | | | Population | 都市人口 | MUL | +-------------+------------+-----+

抽出項目

国コード 都市名 都市人口

抽出仕様

テーブル

Page 79: MySQL勉強会 クエリチューニング編

6章 相関サブクエリは諸刃の剣

クエリの実行計画を確認してみます。

SELECT Cty1.CountryCode, Cty1.Name, Cty1.Population FROM City Cty1 WHERE Cty1.Population = ( SELECT MAX(Cty2.Population) FROM City Cty2 WHERE Cty2.CountryCode = Cty1.CountryCode );

+----+--------------------+-------+------+---------------+-------------+---------+----------------------------+--------+-------------+ | id | select_type | table | type | possible_keys | key | key_len | ref | rows | Extra | +----+--------------------+-------+------+---------------+-------------+---------+----------------------------+--------+-------------+ | 1 | PRIMARY | Cty1 | ALL | NULL | NULL | NULL | NULL | 412116 | Using where | | 2 | DEPENDENT SUBQUERY | Cty2 | ref | CountryCode | CountryCode | 3 | world_big.Cty1.CountryCode | 873 | | +----+--------------------+-------+------+---------------+-------------+---------+----------------------------+--------+-------------+

相関サブクエリで構成されているため、select_typeに DEPENDENT SUBQUERYと表示されています。

+----+--------------------+-------+------+---------------+-------------+---------+----------------------------+--------+-------------+ | id | select_type | table | type | possible_keys | key | key_len | ref | rows | Extra | +----+--------------------+-------+------+---------------+-------------+---------+----------------------------+--------+-------------+ | 1 | PRIMARY | Cty1 | ALL | NULL | NULL | NULL | NULL | 412116 | Using where | | 2 | DEPENDENT SUBQUERY | Cty2 | ref | CountryCode | CountryCode | 3 | world_big.Cty1.CountryCode | 873 | | +----+--------------------+-------+------+---------------+-------------+---------+----------------------------+--------+-------------+

外部クエリ

サブクエリ

実行計画

Page 80: MySQL勉強会 クエリチューニング編

6章 相関サブクエリは諸刃の剣

+-------------+--------------+------------+ | CountryCode | Name | Population | +-------------+--------------+------------+ ① | JPN | Tokyo | 7980230 | ② | JPN | Osaka | 2595674 | ③ | JPN | Kamakura | 167661 | ④ | CHN | Shanghai | 9696300 | ⑤ | CHN | Kunming | 1829500 | ⑥ | CHN | Dali | 136554 | ⑦ | USA | New York | 8008278 | ⑧ | USA | Houston | 1953631 | ⑨ | USA | Hollywood | 139357 | +-------------+--------------+------------+

データの内容

相関サブクエリのイメージを図示すると以下のような感じです。

Cty1

② ①

⑥ ④ ⑤

⑦ ③

Cty2

CountryCode=JPN CountryCode=CHN CountryCode=USA

+-------------+--------------+------------+ | CountryCode | Name | Population | +-------------+--------------+------------+ ① | JPN | Tokyo | 7980230 | ④ | CHN | Shanghai | 9696300 | ⑦ | USA | New York | 8008278 | +-------------+--------------+------------+

クエリ実行結果

SELECT Cty1.CountryCode, Cty1.Name, Cty1.Population FROM City Cty1 WHERE Cty1.Population = ();

SELECT MAX(Cty2.Population) FROM City Cty2 WHERE Cty2.CountryCode = Cty1.CountryCode

Cty1.CountryCodeで Cty2の集合を分割

MAX(Cty2.Population)

+-------------+--------------+------------+ | CountryCode | Name | Population | +-------------+--------------+------------+ ① | JPN | Tokyo | 7980230 | ② | JPN | Osaka | 2595674 | ③ | JPN | Kamakura | 167661 | ④ | CHN | Shanghai | 9696300 | ⑤ | CHN | Kunming | 1829500 | ⑥ | CHN | Dali | 136554 | ⑦ | USA | New York | 8008278 | ⑧ | USA | Houston | 1953631 | ⑨ | USA | Hollywood | 139357 | +-------------+--------------+------------+

Page 81: MySQL勉強会 クエリチューニング編

6章 相関サブクエリは諸刃の剣

テーブルアクセスイメージ

Cty2

Cty1

row2

row1

row412115

row412116

Cty2

Cty2

Cty2

+----+--------------------+-------+------+---------------+-------------+---------+----------------------------+--------+-------------+ | id | select_type | table | type | possible_keys | key | key_len | ref | rows | Extra | +----+--------------------+-------+------+---------------+-------------+---------+----------------------------+--------+-------------+ | 1 | PRIMARY | Cty1 | ALL | NULL | NULL | NULL | NULL | 412116 | Using where | | 2 | DEPENDENT SUBQUERY | Cty2 | ref | CountryCode | CountryCode | 3 | world_big.Cty1.CountryCode | 873 | | +----+--------------------+-------+------+---------------+-------------+---------+----------------------------+--------+-------------+

実行計画

row1

row2

row412115

row412116

・ ・ ・

873件 スキャン

873件 スキャン

873件 スキャン

873件 スキャン

359,777,268件 スキャン!

412116件 スキャン

Page 82: MySQL勉強会 クエリチューニング編

6章 相関サブクエリは諸刃の剣

82

Cty2

①SELECT 'Tokyo' , 'JPN' , 7980230 FROM City WHERE 7980230 = 7980230; ○ ②SELECT 'Osaka' , 'JPN' , 2595674 FROM City WHERE 2595674 = 7980230; ③SELECT 'Kamakura' , 'JPN' , 167661 FROM City WHERE 167661 = 7980230; ④SELECT 'Shanghai' , 'CHN' , 9696300 FROM City WHERE 9696300 = 9696300; ○ ⑤SELECT 'Kunming' , 'CHN' , 1829500 FROM City WHERE 1829500 = 9696300; ⑥SELECT 'Dali' , 'CHN' , 136554 FROM City WHERE 136554 = 9696300; ⑦SELECT 'New York' , 'USA' , 8008278 FROM City WHERE 8008278 = 8008278; ○ ⑧SELECT 'Houston' , 'USA' , 1953631 FROM City WHERE 1953631 = 8008278; ⑨SELECT 'Hollywood' , 'USA' , 139357 FROM City WHERE 139357 = 8008278;

Cty1

② ①

⑥ ④ ⑤

⑦ ③

+-------------+--------------+------------+ | CountryCode | Name | Population | +-------------+--------------+------------+ ① | JPN | Tokyo | 7980230 | ② | JPN | Osaka | 2595674 | ③ | JPN | Kamakura | 167661 | ④ | CHN | Shanghai | 9696300 | ⑤ | CHN | Kunming | 1829500 | ⑥ | CHN | Dali | 136554 | ⑦ | USA | New York | 8008278 | ⑧ | USA | Houston | 1953631 | ⑨ | USA | Hollywood | 139357 | +-------------+--------------+------------+

データの内容

クエリ展開図

Cty1.CountryCode

MAX(Cty2.Population)

SELECT Cty1.CountryCode, Cty1.Name, Cty1.Population FROM City Cty1 WHERE Cty1.Population = ();

SELECT MAX(Cty2.Population) FROM City Cty2 WHERE Cty2.CountryCode = Cty1.CountryCode

相関サブクエリの仕組み

Cty1のタプル数分、 Cty2のクエリ発行を 繰り返す

Page 83: MySQL勉強会 クエリチューニング編

6章 相関サブクエリは諸刃の剣

実行結果 … 返ってきません。

チューニングしましょう。

Page 84: MySQL勉強会 クエリチューニング編

6章 相関サブクエリは諸刃の剣

各国の最大人口を誇る都市を抽出

クエリ City(都市テーブル) (件数増幅:411979)

+-------------+------------+-----+ | ColumnID | ColumnName | Key | +-------------+------------+-----+ | ID | 都市ID | PRI | | Name | 都市名 | | | CountryCode | 国コード | MUL | | District | 地区 | | | Population | 都市人口 | MUL | +-------------+------------+-----+

抽出項目

国コード 都市名 都市人口

抽出仕様

テーブル

SELECT Cty1.CountryCode, Cty1.Name, Cty1.Population FROM City Cty1 INNER JOIN ( SELECT Cty2.CountryCode, MAX(Cty2.Population) Population FROM City Cty2 GROUP BY Cty2.CountryCode ) Sub ON Cty1.CountryCode = Sub.CountryCode AND Cty1.Population = Sub.Population;

抽出イメージ

Cty1 +-------------+--------------+------------+ | CountryCode | Name | Population | +-------------+--------------+------------+ | JPN | Tokyo | 7980230 | | JPN | Osaka | 2595674 | | JPN | Kamakura | 167661 | | CHN | Shanghai | 9696300 | | CHN | Kunming | 1829500 | | CHN | Dali | 136554 | | USA | New York | 8008278 | | USA | Houston | 1953631 | | USA | Hollywood | 139357 | +-------------+--------------+------------+

Sub (Cty2) +-------------+------------+ | CountryCode | Population | +-------------+------------+ | JPN | 7980230 | | CHN | 9696300 | | USA | 8008278 | +-------------+------------+

Cty1 INNER JOIN Sub +-------------+--------------+------------+ | CountryCode | Name | Population | +-------------+--------------+------------+ | JPN | Tokyo | 7980230 | | CHN | Shanghai | 9696300 | | USA | New York | 8008278 | +-------------+--------------+------------+

+ =

Cty1 +-------------+--------------+------------+ | CountryCode | Name | Population | +-------------+--------------+------------+ | JPN | Tokyo | 7980230 | | JPN | Osaka | 2595674 | | JPN | Kamakura | 167661 | | CHN | Shanghai | 9696300 | | CHN | Kunming | 1829500 | | CHN | Dali | 136554 | | USA | New York | 8008278 | | USA | Houston | 1953631 | | USA | Hollywood | 139357 | +-------------+--------------+------------+

Page 85: MySQL勉強会 クエリチューニング編

6章 相関サブクエリは諸刃の剣

クエリの実行計画を確認してみます。

SELECT Cty1.CountryCode, Cty1.Name, Cty1.Population FROM City Cty1 INNER JOIN ( SELECT Cty2.CountryCode, MAX(Cty2.Population) Population FROM City Cty2 GROUP BY Cty2.CountryCode ) Sub ON Cty1.CountryCode = Sub.CountryCode AND Cty1.Population = Sub.Population;

+----+-------------+------------+-------+------------------------+-------------+---------+-------------------+--------+-------------+ | id | select_type | table | type | possible_keys | key | key_len | ref | rows | Extra | +----+-------------+------------+-------+------------------------+-------------+---------+-------------------+--------+-------------+ | 1 | PRIMARY | <derived2> | ALL | NULL | NULL | NULL | NULL | 232 | | | 1 | PRIMARY | Cty1 | ref | CountryCode,Population | Population | 4 | Sub.Population | 1 | Using where | | 2 | DERIVED | Cty2 | index | NULL | CountryCode | 3 | NULL | 412116 | | +----+-------------+------------+-------+------------------------+-------------+---------+-------------------+--------+-------------+

実行時間は以下の通りです。

232 rows in set (0.76 sec)

Page 86: MySQL勉強会 クエリチューニング編

6章 相関サブクエリは諸刃の剣

なんで速くなったの?

Page 87: MySQL勉強会 クエリチューニング編

6章 相関サブクエリは諸刃の剣

テーブルアクセスイメージ

+----+-------------+------------+-------+------------------------+-------------+---------+----------------+--------+-------------+ | id | select_type | table | type | possible_keys | key | key_len | ref | rows | Extra | +----+-------------+------------+-------+------------------------+-------------+---------+----------------+--------+-------------+ | 1 | PRIMARY | <derived2> | ALL | NULL | NULL | NULL | NULL | 232 | | | 1 | PRIMARY | Cty1 | ref | CountryCode,Population | Population | 4 | Sub.Population | 1 | Using where | | 2 | DERIVED | Cty2 | index | NULL | CountryCode | 3 | NULL | 412116 | | +----+-------------+------------+-------+------------------------+-------------+---------+----------------+--------+-------------+

チューニング後の実行計画

Cty1 Cty2 derived2

412116件 スキャン

232件 232 * 1件 スキャン

Page 88: MySQL勉強会 クエリチューニング編

6章 まとめ

相関サブクエリは便利かつ、 それでしか実現できない抽出条件も存在する! しかし、ボトルネックの温床!

Page 89: MySQL勉強会 クエリチューニング編

1章 対話的にクエリを作る

2章 オプティマイザの判断

3章 サブクエリは「データ」の集合

4章 ソートとインデックス

5章 インデックスは万能ではない

6章 相関サブクエリは諸刃の剣

休憩 最後に

Page 90: MySQL勉強会 クエリチューニング編

Microadの職人たちを紹介します。

※画像クリックでリンク先に遷移します。

2013年6月時点のサイトトップ

Page 91: MySQL勉強会 クエリチューニング編

御清聴ありがとうございました