Upload
microad-incengineer
View
2.491
Download
1
Embed Size (px)
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章 相関サブクエリは諸刃の剣
休憩 最後に
御清聴ありがとうございました