32
Index Shotgun on MySQL 5.6(provisional) 2013/04/17 yoku0825 Modified version 2

Index shotgun on mysql5.6

Embed Size (px)

DESCRIPTION

2013/04/17 MySQL Casual Talks #4の資料です。 当日ぐだぐだにしゃべったのよりもう少し状況の説明をしています。。遅くなりましたorz

Citation preview

Index Shotgun

on MySQL 5.6(provisional)

2013/04/17

yoku0825

Modified version 2

I’m yoku0825,

working as DBA

for the company’s web-service.

My wife’s husband, my son’s father,

lovin’ MySQL and Hannari-Tofu too much.

I can’t even log in to PostgreSQL and Oracle but I’m fine! :)

Have you read

`SQL Antipatterns’?

`Index Shotgun’ is one of the

Antipatterns described in it.

In Japanese.

(;´д`)索引大杉

And I saw `Shotguned Index' at

our MySQL.

なんとIndex_lengthがData_lengthの4倍。

( ´-`).oO(標準から見て

4倍が多すぎるのかどうかは

判らない

だけど、本の索引が本文の4倍もあったら嫌だ

By the way, Why is shotguned

index Antipattern?

Or "How much does it decrease

performance?"

測ってみた

CPU

Xeon L5520 2.27GHz 1P4C8T

Memory

12GiB(don't know any details)

Storage

RAID Controller(RAID5, 8PD)

read 2450MiB/s, 3950IOPS

write 2450MiB/s, 3900IOPS

ext4 on LVM(noatimeしてない)

my.cnf

[mysqld]

query-cache-type = 0

loose-innodb-buffer-pool-size = 1G

loose-innodb-buffer-pool-instances = 1

loose-innodb-log-file-size = 128M

loose-innodb-file-per-table = 1

loose-innodb-file-format = barracuda

測ってみた

+--------------+------------------+------+-----+-------------------+-------+

| Field | Type | Null | Key | Default | Extra |

+--------------+------------------+------+-----+-------------------+-------+

| num | int(10) unsigned | NO | | 0 | |

| md5 | varchar(32) | YES | | NULL | |

| sha | varchar(40) | YES | | NULL | |

| old_password | varchar(16) | YES | | NULL | |

| password | varchar(41) | YES | | NULL | |

| timeval | timestamp | NO | | CURRENT_TIMESTAMP | |

+--------------+------------------+------+-----+-------------------+-------+

1行あたり140bytes。

テストデータ1000万行をひたすらINSERTする苦行。

データは1.4GiB。

InnoDB(5.5) - INSERT duration

0:00:00

0:07:12

0:14:24

0:21:36

0:28:48

0:36:00

0:43:12

0:50:24

0:57:36

1:04:48

1:12:00

no key pri(4) pri(4) +

1key(32)

pri(4) +

1key(72)

pri(4) +

1key(88)

pri(4) +

1key(129)

pri(4) +

3key(12)

pri(4) +

3key(76)

pri(4) +

3key(144)

pri(4) +

3key(160)

InnoDB(5.5) - size

0

1,000,000,000

2,000,000,000

3,000,000,000

4,000,000,000

5,000,000,000

6,000,000,000

no key pri(4) pri(4) +

1key(32)

pri(4) +

1key(72)

pri(4) +

1key(88)

pri(4) +

1key(129)

pri(4) +

3key(12)

pri(4) +

3key(76)

pri(4) +

3key(144)

pri(4) +

3key(160)

• 72byteのKey1つと合計76byteになるKey3つで

はそんなに変わらなさそう。

• 1行140bytesしかないテーブルなので、イン

デックスの作成がもろにテーブル全体のサイ

ズに影響する

–社内で出会った炸裂インデックスももともとは.ibd

ファイルでっかくね?からだった。

散弾銃索引退治

• 取り敢えず重複インデックスがあったから消した(基本)

• 本当はもうちょっと削りたい。–なんか上手い具合にINDEXの使われ度合いを測

れないもんかな?

– Percona ServerやMariaDBには、そのインデックス

を使って何行フェッチしたか統計を取る

information_schemaが突っ込まれている。

pt-duplicate-key-entry

Percona Toolkitの1つで、重複インデックスを検出してくれる。

$ pt-duplicate-key-checker S=/usr/mysql/5.5.30/data/mysql.sock,u=tpcc,p=xxxx --database=tpcc

# ######################################################################### tpcc.stock# ########################################################################

# s_w_id is a left-prefix of PRIMARY# Key definitions:# KEY `s_w_id` (`s_w_id`),# PRIMARY KEY (`s_w_id`,`s_i_id`),# Column types:# `s_w_id` smallint(6) not null# `s_i_id` int(11) not null# To remove this duplicate index, execute:ALTER TABLE `tpcc`.`stock` DROP INDEX `s_w_id`;

# ######################################################################### Summary of indexes# ########################################################################

# Size Duplicate Indexes 2# Total Duplicate Indexes 1# Total Indexes 25

http://www.percona.com/doc/percona-toolkit/2.1/pt-duplicate-key-checker.html

まずこれを退治。

information_schema.INNODB_BUFFER_PAGE

• 5.6.2から搭載された

– 5.5.28, 5.1.66にもバックポートされてる(5.1.66はplugin-loadで食わせてやる必要がある)

• InnoDB Buffer Poolにどんなページが載ってるのかがinformation_schemaからアクセスできる。

– これで載ってるインデックスページ調べれば幸せになれるんじゃない?とか思った。

information_schema.INNODB_BUFFER_PAGE_LRU

+---------------------+---------------------+------+-----+---------+-------+

| Field | Type | Null | Key | Default | Extra |

+---------------------+---------------------+------+-----+---------+-------+

| POOL_ID | bigint(21) unsigned | NO | | 0 | |

| LRU_POSITION | bigint(21) unsigned | NO | | 0 | | LRUリストの位置

| SPACE | bigint(21) unsigned | NO | | 0 | |

| PAGE_NUMBER | bigint(21) unsigned | NO | | 0 | |

| PAGE_TYPE | varchar(64) | YES | | NULL | | UNDO_LOG, INDEX, SYSTEMとか

| FLUSH_TYPE | bigint(21) unsigned | NO | | 0 | |

| FIX_COUNT | bigint(21) unsigned | NO | | 0 | |

| IS_HASHED | varchar(3) | YES | | NULL | |

| NEWEST_MODIFICATION | bigint(21) unsigned | NO | | 0 | | このページを最後に更新したLSN

| OLDEST_MODIFICATION | bigint(21) unsigned | NO | | 0 | | このページを最初に更新したLSN

| ACCESS_TIME | bigint(21) unsigned | NO | | 0 | | 初めてバッファプールに載った時間

| TABLE_NAME | varchar(1024) | YES | | NULL | |

| INDEX_NAME | varchar(1024) | YES | | NULL | |

| NUMBER_RECORDS | bigint(21) unsigned | NO | | 0 | | そのページに載っている行

| DATA_SIZE | bigint(21) unsigned | NO | | 0 | | そのページに載っているByte数

| COMPRESSED_SIZE | bigint(21) unsigned | NO | | 0 | |

| COMPRESSED | varchar(3) | YES | | NULL | |

| IO_FIX | varchar(64) | YES | | NULL | |

| IS_OLD | varchar(3) | YES | | NULL | | OLDページかどうか

| FREE_PAGE_CLOCK | bigint(21) unsigned | NO | | 0 | | ページがLRUリストから落ちるとインクリメント

+---------------------+---------------------+------+-----+---------+-------+

information_schema.INNODB_BUFFER_PAGE_LRU

• とりあえず起動直後の状態をチェック。innodb_buffer_pool_size = 5M(=256ページ)

mysql56> SELECT lru_position, table_name, index_name, number_records AS num, data_size, is_old, newest_modification AS LSN, access_time FROM

innodb_buffer_page_lru WHERE page_type = 'INDEX';

+--------------+--------------------------------+-----------------------+-----+-----------+--------+-----+-------------+

| lru_position | table_name | index_name | num | data_size | is_old | LSN | access_time |

+--------------+--------------------------------+-----------------------+-----+-----------+--------+-----+-------------+

| 210 | `SYS_IBUF_TABLE` | CLUST_IND | 0 | 0 | NO | 0 | 1153626747 |

| 218 | `mysql`.`innodb_index_stats` | PRIMARY | 46 | 4060 | NO | 0 | 1153626732 |

| 219 | `mysql`.`innodb_table_stats` | PRIMARY | 11 | 635 | NO | 0 | 1153626720 |

| 220 | `SYS_TABLES` | ID_IND | 20 | 613 | NO | 0 | 1153626698 |

| 221 | `SYS_TABLES` | CLUST_IND | 20 | 1513 | NO | 0 | 1153626661 |

| 222 | `SYS_COLUMNS` | CLUST_IND | 103 | 6784 | NO | 0 | 1153626696 |

| 223 | `SYS_DATAFILES` | SYS_DATAFILES_SPACE | 16 | 766 | NO | 0 | 1153626748 |

| 224 | `SYS_TABLESPACES` | SYS_TABLESPACES_SPACE | 16 | 750 | NO | 0 | 1153626748 |

| 225 | `SYS_INDEXES` | CLUST_IND | 25 | 1715 | NO | 0 | 1153626695 |

| 240 | `SYS_FIELDS` | CLUST_IND | 30 | 1274 | NO | 0 | 1153626696 |

| 241 | `SYS_FOREIGN` | FOR_IND | 4 | 148 | NO | 0 | 1153626696 |

| 242 | `SYS_FOREIGN` | ID_IND | 4 | 280 | NO | 0 | 1153626768 |

| 243 | `SYS_FOREIGN_COLS` | ID_IND | 4 | 246 | NO | 0 | 1153626768 |

| 244 | `SYS_FOREIGN` | REF_IND | 4 | 152 | NO | 0 | 1153626696 |

| 245 | `test`.`item` | PRIMARY | 17 | 938 | NO | 0 | 1153626777 |

| 247 | `mysql`.`slave_master_info` | PRIMARY | 0 | 0 | NO | 0 | 1153626963 |

| 250 | `mysql`.`slave_worker_info` | PRIMARY | 0 | 0 | NO | 0 | 1153626975 |

| 253 | `mysql`.`slave_relay_log_info` | PRIMARY | 0 | 0 | NO | 0 | 1153626984 |

+--------------+--------------------------------+-----------------------+-----+-----------+--------+-----+-------------+

18 rows in set (0.01 sec)

– lru_positionは大きい方が最近ぽい。

– そういえばmysql.slave_*はInnoDBだっけ。

information_schema.INNODB_BUFFER_PAGE_LRU

• テーブル1つ(test.brand)スキャンしてみる。

mysql56> SELECT lru_position, table_name, index_name, number_records AS num, data_size, is_old, newest_modification AS LSN, access_time FROM

innodb_buffer_page_lru WHERE page_type = 'INDEX';

+--------------+--------------------------------+-----------------------+-----+-----------+--------+-----+-------------+

| lru_position | table_name | index_name | num | data_size | is_old | LSN | access_time |

+--------------+--------------------------------+-----------------------+-----+-----------+--------+-----+-------------+

| 208 | `SYS_IBUF_TABLE` | CLUST_IND | 0 | 0 | NO | 0 | 1153626747 |

| 216 | `mysql`.`innodb_table_stats` | PRIMARY | 11 | 635 | NO | 0 | 1153626720 |

| 217 | `SYS_TABLES` | ID_IND | 20 | 613 | NO | 0 | 1153626698 |

| 218 | `SYS_DATAFILES` | SYS_DATAFILES_SPACE | 16 | 766 | NO | 0 | 1153626748 |

| 219 | `SYS_TABLESPACES` | SYS_TABLESPACES_SPACE | 16 | 750 | NO | 0 | 1153626748 |

| 234 | `test`.`item` | PRIMARY | 17 | 938 | NO | 0 | 1153626777 |

| 236 | `mysql`.`slave_master_info` | PRIMARY | 0 | 0 | NO | 0 | 1153626963 |

| 239 | `mysql`.`slave_worker_info` | PRIMARY | 0 | 0 | NO | 0 | 1153626975 |

| 242 | `mysql`.`slave_relay_log_info` | PRIMARY | 0 | 0 | NO | 0 | 1153626984 |

| 245 | `SYS_TABLES` | CLUST_IND | 20 | 1513 | NO | 0 | 1153626661 |

| 246 | `SYS_COLUMNS` | CLUST_IND | 103 | 6784 | NO | 0 | 1153626696 |

| 247 | `SYS_INDEXES` | CLUST_IND | 25 | 1715 | NO | 0 | 1153626695 |

| 248 | `SYS_FIELDS` | CLUST_IND | 30 | 1274 | NO | 0 | 1153626696 |

| 249 | `SYS_FOREIGN` | FOR_IND | 4 | 148 | NO | 0 | 1153626696 |

| 250 | `SYS_FOREIGN` | ID_IND | 4 | 280 | NO | 0 | 1153626768 |

| 251 | `SYS_FOREIGN_COLS` | ID_IND | 4 | 246 | NO | 0 | 1153626768 |

| 252 | `SYS_FOREIGN` | REF_IND | 4 | 152 | NO | 0 | 1153626696 |

| 253 | `mysql`.`innodb_index_stats` | PRIMARY | 46 | 4060 | NO | 0 | 1153626732 |

| 254 | `test`.`brand` | PRIMARY | 10 | 575 | NO | 0 | 1154017292 |

+--------------+--------------------------------+-----------------------+-----+-----------+--------+-----+-------------+

19 rows in set (0.01 sec)

– lru_position 254に載ったから、やっぱりこっちがyoung側の気がする。

– でもミッドポイント挿入戦略とかいうのがなかったっけ。

– SELECTしただけだからnewest_modificationはからっぽ、access_timeは記録される。

information_schema.INNODB_BUFFER_PAGE_LRU

• test.brandに1行INSERTしてみる。

mysql56> SELECT lru_position, table_name, index_name, number_records AS num, data_size, is_old, newest_modification AS LSN, access_time FROM

innodb_buffer_page_lru WHERE page_type = 'INDEX';

+--------------+--------------------------------+-----------------------+-----+-----------+--------+-----------+-------------+

| lru_position | table_name | index_name | num | data_size | is_old | LSN | access_time |

+--------------+--------------------------------+-----------------------+-----+-----------+--------+-----------+-------------+

| 202 | `SYS_IBUF_TABLE` | CLUST_IND | 0 | 0 | NO | 0 | 1153626747 |

| 210 | `mysql`.`innodb_table_stats` | PRIMARY | 11 | 635 | NO | 0 | 1153626720 |

| 211 | `SYS_TABLES` | ID_IND | 20 | 613 | NO | 0 | 1153626698 |

| 212 | `SYS_DATAFILES` | SYS_DATAFILES_SPACE | 16 | 766 | NO | 0 | 1153626748 |

| 213 | `SYS_TABLESPACES` | SYS_TABLESPACES_SPACE | 16 | 750 | NO | 0 | 1153626748 |

| 228 | `test`.`item` | PRIMARY | 17 | 938 | NO | 0 | 1153626777 |

| 230 | `mysql`.`slave_master_info` | PRIMARY | 0 | 0 | NO | 0 | 1153626963 |

| 233 | `mysql`.`slave_worker_info` | PRIMARY | 0 | 0 | NO | 0 | 1153626975 |

| 236 | `mysql`.`slave_relay_log_info` | PRIMARY | 0 | 0 | NO | 0 | 1153626984 |

| 239 | `SYS_TABLES` | CLUST_IND | 20 | 1513 | NO | 0 | 1153626661 |

| 240 | `SYS_COLUMNS` | CLUST_IND | 103 | 6784 | NO | 0 | 1153626696 |

| 241 | `SYS_INDEXES` | CLUST_IND | 25 | 1715 | NO | 0 | 1153626695 |

| 242 | `SYS_FIELDS` | CLUST_IND | 30 | 1274 | NO | 0 | 1153626696 |

| 243 | `SYS_FOREIGN` | FOR_IND | 4 | 148 | NO | 0 | 1153626696 |

| 244 | `SYS_FOREIGN` | ID_IND | 4 | 280 | NO | 0 | 1153626768 |

| 245 | `SYS_FOREIGN_COLS` | ID_IND | 4 | 246 | NO | 0 | 1153626768 |

| 246 | `SYS_FOREIGN` | REF_IND | 4 | 152 | NO | 0 | 1153626696 |

| 247 | `mysql`.`innodb_index_stats` | PRIMARY | 46 | 4060 | NO | 0 | 1153626732 |

| 248 | `test`.`brand` | PRIMARY | 11 | 614 | NO | 319272605 | 1154017292 |

| 252 | `test`.`factory` | PRIMARY | 4 | 254 | NO | 0 | 1154232813 |

| 254 | `test`.`brand` | maker | 11 | 360 | NO | 319272654 | 1154232814 |

+--------------+--------------------------------+-----------------------+-----+-----------+--------+-----------+-------------+

21 rows in set (0.01 sec)

– test.factoryが読み込まれたのは、FOREIGN KEYを切ってるからっぽい(FOREIGN KEYを消すと載らない)

– PRIMARY INDEXとmaker INDEXがそれぞれLSNが記録されてる。

ということは

• LSNが記録されていない(=0)のレコードはSELECTだけされてるやつだから使われているって根拠になりそう。

– LNS <> 0で検索してみようか。

• SELECTで特定のINDEXを使った場合はそのINDEX(とデータフェッチ用にPRIMARY KEY)だけが載るので、テーブル上の全部のインデックスがリストされている = INSERTで載ったと思えそう。– information_schema.statisticsとJOINして相関サブクエリでゴニョゴニョやろうとしたらものすごく重くなったので断念。。

– information_schemaってテンポラリテーブルみたいに毎回データをストアしてるぽいので、相関サブクエリだと確か

に悲惨になるよね。。

と思ったんですが

• 本番に5.5.28(以降)が2台しかなかった。

• しかもその2台(Master & Slave)はバッファプールがガラガラだった。

• とりあえず、newest_modification = 0のページは無かった。。– 一度LRUリストから落ちて次に読み込まれると、

newest_modificationは0に戻るのね。

• あと、InnoDB Compressed使ってるのですんごくinnodb_buffer_page_lruへのアクセスが重い。

• Master & Slaveで分散させてると、そのチェック結果をマージしないとダメそうだよね。。

• あと、access_timeがミリ秒単位のUNIXTIMEを32bit unsignedで受け取っているので、49.7日くらいでオーバーフローする。。

Percona Serverの

information_schema. INDEX_STATISTICSみたいに

ずぱっとどれくらい使ってるのか判るわけじゃない。

mysql> SET GLOBAL userstat=on;

..

mysql> SELECT * FROM index_statistics;

+--------------+------------+------------+-----------+

| TABLE_SCHEMA | TABLE_NAME | INDEX_NAME | ROWS_READ |

+--------------+------------+------------+-----------+

| tpcc | item | PRIMARY | 100000 |

+--------------+------------+------------+-----------+

1 row in set (0.00 sec)

いやこれもどのインデックス使ってないかっていう銀の弾丸じゃないけど。

上手く考察する方法求む

By the way,

Where does it go “on MySQL

5.6(provisional)”?

5.5と5.6で比較したんですが、

有意差が出ないという

`比べてみた’ネタとしては最悪の結果になったので

Upgrading 5.6 from 5.5 hasn’t effect on

shotguned indexes.

という結論になりました。

ごめんなさいごめんなさい。

でもinnodb_buffer_page_lruは楽しかったです。

上手いこと分析したい。

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