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

Preview:

DESCRIPTION

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

Citation preview

MicroAd

マイクロアド

薮下 和弥 システム開発部

MySQL 勉強会

クエリチューニング編

はじめに

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

当勉強会で…

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

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

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

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

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

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

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

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

休憩

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

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

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

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

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

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

休憩

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

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 | +------------+

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

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

実 行 結 果

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

ちょっと待った

速いの?

このクエリ

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

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

速いッ!

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

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

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

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

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

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

EXPLAIN select_query;

実行計画取得コマンド

言わば、

なのです。

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が使われてない!テーブルスキャン!!

外部クエリ

サブクエリ

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

何がダメなの? |ω・)

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'の行を探してください。

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

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

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

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

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

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

これが、INDEXッ!!

+-------------+------------+----------+------+-----+ | 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' );

+----+-------------+-------+------+---------------+------+---------+------+------+-------------+ | 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を利用した検索が可能に!

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

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 | +----+-------------+-------+------+---------------+-------------+---------+-------+------+-------------+

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を使用することが出来ないのです。

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;

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

抽出の仕様に影響なし

クエリ

+----+-------------+-------+------+---------------+-------------+---------+-------+------+-------------+ | 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を利用した範囲検索が可能に!

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

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

仕上げの実行結果確認

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

実 行 結 果

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

ちなみに・・・

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述語を使用(前方一致は使用可能)

・ ・ ・

1章 まとめ

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

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

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

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

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

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

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

休憩

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 |○ +------+------------+-------------+----------------+------------+

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

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

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;

…あれっ!?

実行計画

実行計画は…

+----+-------------+-------+------+---------------+------+---------+------+------+-------------+ | 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章のチューニング前の実行計画

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

では、なぜ?

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

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

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

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を利用した方が効率が良いと判断したようです。

実行計画

2章 まとめ

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

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

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

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

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

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

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

休憩

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 |○ +------------+-------------+------------+

+ =

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が使われていません。

実行計画

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は使われません。

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

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

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というデータの集合(テンポラリテーブル)

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 | +---------------+------------+------------+

+----+-------------+------------+------+---------------+------+---------+------+------+-------------+ | 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が可能に!

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

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

ちなみに・・・

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が… 使われている!?

実行計画

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

3章 まとめ

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

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

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

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

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

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

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

休憩

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

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

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

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

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

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

休憩

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 |○ +------+----------+-------------+

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 | +----+-------------+-------+------+---------------+-------------+---------+--------------------+------+---------------------------------+

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

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

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

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);

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を用いたソートが可能に!

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

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

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順に行を取り出したその時、既にソートは終わっているのです。

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

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

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

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

CodeのみのINDEX情報

head

AAA CCC BBB Code

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

② ③

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

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

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

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

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順にソートする必要が出てきます。

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

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

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

68

実は・・・

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

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

4章 まとめ

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

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

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

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

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

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

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

休憩

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)

※データ量増

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 |○ +-------------+-----------+

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

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がテーブルスキャンとなっているが負荷は軽微!

実行計画

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)

5章 まとめ

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

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

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

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

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

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

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

休憩

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 | +-------------+------------+-----+

抽出項目

国コード 都市名 都市人口

抽出仕様

テーブル

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 | | +----+--------------------+-------+------+---------------+-------------+---------+----------------------------+--------+-------------+

外部クエリ

サブクエリ

実行計画

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 | +-------------+--------------+------------+

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件 スキャン

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のクエリ発行を 繰り返す

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

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

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

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 | +-------------+--------------+------------+

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)

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

なんで速くなったの?

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件 スキャン

6章 まとめ

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

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

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

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

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

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

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

休憩 最後に

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

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

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

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