Upgrade to Pro — share decks privately, control downloads, hide ads and more …

テストデータが偏るということについて

 テストデータが偏るということについて

2022/05/18
サイボウズ株式会社 開運研修の1コマ

yoku0825

May 31, 2022
Tweet

More Decks by yoku0825

Other Decks in Technology

Transcript

  1. テストデータが偏るということについて
     
    2022/05/18
    yoku0825
    サイボウズ開運研修

    View Slide

  2. ごあんない
    このセッションはデータベース一般の話ではなく、MySQLの話です
    ググれば出てくるような、「SQLの書き方」や「MySQLの設定」みたいな話は出てきません
    今回は「インデックスに紐づくレコード件数の偏りが実行計画に及ぼす影響」の話です
    これは「あ、いつかそういえばそんなこと聞いたことあるな」と思ってもらうためのセッションです

    最近身の回りでこの手の話が何回か出てきたので、いつかそんな状況になった時に思い出してもらえれば幸いで


    1/35

    View Slide

  3. \こんにちわ/
    yoku0825@とある企業のDBA
    オラクらない

    ポスグらない

    マイエスキューエる

    生息域
    Twitter: @yoku0825

    Blog: 日々の覚書

    日本MySQLユーザ会

    MySQL Casual

    Siriusチームのスレッド

    2/35

    View Slide

  4. 基本的な話
    どんなにダメなテーブル定義をしても、データの件数が少なければ問題にはならない
    データの件数が大きくなってきても最も効率の良いインデックスで少数の行をフェッチする「得意なア
    クセスパターン」であれば十分な性能が出る
    裏を返すと、テーブルの件数が大きくなって最も効率の良いインデックスで少数の行をフェッチするアクセスパターン
    でない場合は性能が出なくなってくる

    3/35

    View Slide

  5. 最も効率の良いインデックス
    (GROUP BYを除いて) 「走査する行の数 = 結果セットとして送信する行の数」になるインデック
    スが最良
    LIMITなしならWHEREの条件をすべて刈り込めるインデックス
    LIMITありならWHEREで刈り込みつつORDER BY .. LIMITの最適化を効かせられるインデッ
    クス
    See also,
    MySQLが得意なこと、不得意なこと(仮)

    4/35

    View Slide

  6. インデックスの選ばれ方
    オプティマイザという名前のブラックボックスに隠されている
    「どの実行計画(アクセスパス)が一番行の走査を減らせるか」を計算し続けている
    5/35

    View Slide

  7. オプティマイザの弱点
    データの偏りを完全には把握していない
    InnoDBの統計情報は1インデックスあたりデフォルト20ページのサンプリングで作る
    そりゃまあ毎回インデックスを完全にスキャンして偏りを把握させるわけにはいかないだろうけれども

    基本的にテーブル内に一定割合 / 一定行数の更新があった時にバックグラウンドスレッドで統計情報を更新す

    「ギリギリ一定割合 / 一定行数に届かない大量更新」があった時が一番ズレる

    演算は基本的に構ってくれない
    INT型で桁あふれが発生しない範囲なら、 id - 1 = 0 ⇔ id = 1 …

    DATETIME型で桁あふれが発生しない範囲なら、 ORDER BY created_at と ORDER BY
    created_at + INTERVAL 1 DAY は同じ順番になるはず…

    6/35

    View Slide

  8. 今回のお題
    7/35

    View Slide

  9. データの偏り
    と実行計画
    8/35

    View Slide

  10. データが偏る #とは
    あるインデックスに紐づくレコードは均一に分散するとは限らない
    ex. SELECT COUNT(*) FROM 住民 WHERE 都道府県 = ?

    ある偏りについてオプティマイザが同じ実行計画を選ぶとは限らない
    ex. SELECT * FROM 住民 WHERE 都道府県 = '東京' ORDER BY birthday DESC LIMIT 10
    万 vs. SELECT * FROM 住民 WHERE 都道府県 = '鳥取' ORDER BY birthday DESC LIMIT
    10万
    たぶん東京はORDER BYを狙った方が速くて、鳥取はWHEREを狙った方が速い(2020年の鳥取の人口は全国の0.4%らしい)
    都道府県の人口一覧 - Wikipedia

    ex. SELECT * FROM 住民 WHERE 都道府県 = '東京' ORDER BY birthday DESC LIMIT 10
    vs. SELECT * FROM 住民 WHERE 都道府県 = '東京' ORDER BY birthday DESC LIMIT 1億
    LIMIT 1億だと日本総人口にかなり近いので全件フェッチの実行計画を選ぶが、LIMITの早抜けが効かないのでORDER BY狙いの
    キーは高速化に寄与できない

    だが、偏りに気が付かずにオプティマイザが実行計画を選んでしまうことはある
    9/35

    View Slide

  11. データが偏る #と何が起こるのか
    期待したレスポンスタイムが得られない場合がある
    1行をフェッチするSELECTと100万行をフェッチするSELECTのレスポンスタイムは当然桁違いになる

    1行をフェッチするのに最適な実行計画と、100万行をフェッチするのに最適な実行計画は違うことがある

    オプティマイザが実行計画を取り違える場合もあれば、この実行計画の差を認識せずにインデックスが足りない場
    合もある

    期待したロック範囲が得られない場合がある
    InnoDBのネクストキーロックは「走査したインデックスレコードとそのギャップ」にロックを置く

    必要最小限の行フェッチで済ませられないとロック範囲は大きくなる

    10/35

    View Slide

  12. TL;DR
    実際問題、データは偏る
    ライトユーザー vs. ヘビーユーザー

    偏ったデータが本番環境に及ぼす影響を把握する
    そもそもベストな状況は何なのかをある程度認識しないといけない

    最大の結果セット、最小の結果セット、あり得ない(はずの)結果セットで比較する

    LIMITの値、INの数も最大と最小を比較するのが吉

    せっかく本番相当のデータを用意しても「よくあるケース」だけのテストでは性能テストにならない

    11/35

    View Slide

  13. 偏ったデータ
    まあシンプルなやつ
    mysql> SHOW CREATE TABLE post\G
    *************************** 1. row ***************************
    Table: post
    Create Table: CREATE TABLE `post` (
    `post_id` int NOT NULL,
    `user_id` int NOT NULL,
    `post_text` text COLLATE utf8mb4_ja_0900_as_cs NOT NULL,
    `registered` datetime NOT NULL,
    PRIMARY KEY (`post_id`),
    KEY `user_id` (`user_id`),
    KEY `registered` (`registered`),
    CONSTRAINT `post_ibfk_1` FOREIGN KEY (`user_id`) REFERENCES `user` (`user_id`) ON DELETE CASCADE
    ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_ja_0900_as_cs
    1 row in set (0.01 sec)
    mysql> SELECT user_id, COUNT(*) FROM post GROUP BY user_id;
    +---------+----------+
    | user_id | COUNT(*) |
    +---------+----------+
    | 1 | 100001 |
    | 2 | 1 |
    +---------+----------+
    2 rows in set (0.04 sec)
    12/35

    View Slide

  14. 偏ったデータ
    +---------+----------+
    | user_id | COUNT(*) |
    +---------+----------+
    | 1 | 100001 |
    | 2 | 1 |
    +---------+----------+
    mysql> EXPLAIN DELETE FROM post WHERE user_id = 2;
    +----+-------------+-------+------------+-------+---------------+---------+---------+-------+------+-
    ---------+-------------+
    | id | select_type | table | partitions | type | possible_keys | key | key_len | ref | rows | filtered | Extra |
    +----+-------------+-------+------------+-------+---------------+---------+---------+-------+------+-
    ---------+-------------+
    | 1 | DELETE | post | NULL | range | user_id | user_id | 4 | const | 1 | 100.00 | Using where |
    +----+-------------+-------+------------+-------+---------------+---------+---------+-------+------+-
    ---------+-------------+
    1 row in set, 1 warning (0.00 sec)
    mysql> DELETE FROM post WHERE user_id = 2;
    13/35

    View Slide

  15. 偏ったデータ
    +---------+----------+
    | user_id | COUNT(*) |
    +---------+----------+
    | 1 | 100001 |
    | 2 | 1 |
    +---------+----------+
    mysql> EXPLAIN DELETE FROM post WHERE user_id = 1;
    +----+-------------+-------+------------+------+---------------+------+---------+------+-------+-----
    -----+-------------+
    | id | select_type | table | partitions | type | possible_keys | key | key_len | ref | rows | filtered | Extra |
    +----+-------------+-------+------------+------+---------------+------+---------+------+-------+-----
    -----+-------------+
    | 1 | DELETE | post | NULL | ALL | user_id | NULL | NULL | NULL | 99875 | 100.00 | Using where |
    +----+-------------+-------+------------+------+---------------+------+---------+------+-------+-----
    -----+-------------+
    1 row in set, 1 warning (0.00 sec)
    mysql> DELETE FROM post WHERE user_id = 1;
    14/35

    View Slide

  16. 偏ったデータ対策
    「可能な限り本番データと同じものでテストする」だけではちょっと足りてない
    本番データだろうと user_id = 2 みたいなやつを対象にしていたら性能は十分足りるはず

    我々があぶりだしたいのは user_id = 1 みたいに偏ったやつ

    ユニットテストではなく、「性能試験」や「負荷試験」と呼ばれるものの対策
    目的が違うので…

    15/35

    View Slide

  17. 偏ったデータ対策
    「あり得る最大の行」「あり得る最小の行」「あり得ない行」の3パターンでテストするとまあまあ検出
    できるはず
    SELECT COUNT(*) FROM post WHERE user_id = ? AND registered BETWEEN ? AND ? + INTERVAL 1
    MONTH;
    SELECT * FROM post WHERE user_id = ? AND registered BETWEEN ? AND ? + INTERVAL 1 MONTH O
    RDER BY registered LIMIT 100 FOR UPDATE;
    16/35

    View Slide

  18. データの偏りを調べる
    user_idだけで見ると
    mysql> SELECT user_id, COUNT(*) FROM post GROUP BY user_id;
    +---------+----------+
    | user_id | COUNT(*) |
    +---------+----------+
    | 1 | 100001 |
    | 2 | 1 |
    +---------+----------+
    2 rows in set (0.03 sec)
    17/35

    View Slide

  19. データの偏りを調べる
    registeredは元のクエリが1か月単位で切りそうだったから年月に丸めてみた
    もとのクエリが INTERVAL 1 MONTH だから、 2021/1/30 ~ 2021/2/28 みたいなまたいだものも本来
    はあるけれども

    mysql> SELECT DATE_FORMAT(registered, '%Y%m') AS yyyymm, COUNT(*) FROM post GROUP BY yyyymm ORDER BY COUNT(*) ASC LI
    MIT 3;
    +--------+----------+
    | yyyymm | COUNT(*) |
    +--------+----------+
    | 200302 | 28 |
    | 200102 | 28 |
    | 200202 | 28 |
    +--------+----------+
    3 rows in set (0.72 sec)
    mysql> SELECT DATE_FORMAT(registered, '%Y%m') AS yyyymm, COUNT(*) FROM post GROUP BY yyyymm ORDER BY COUNT(*) DESC
    LIMIT 3;
    +--------+----------+
    | yyyymm | COUNT(*) |
    +--------+----------+
    | 202205 | 91847 |
    | 200005 | 31 |
    | 200003 | 31 |
    +--------+----------+
    3 rows in set (0.70 sec)
    18/35

    View Slide

  20. データの偏りを調べる
    もうちょっと真面目にやるなら、 user_id = 1 は実は「2020年に引退したからそれ以降のレコー
    ドはない」とかいう偏りもある
    しかしテストとしてそれを調べ始めると調べるだけで日が暮れてしまうので簡易に推測することにす
    る…
    最大の結果セット 最小の結果セット 存在しない結果セット
    user_id 1 2 3
    registered 2022/05/x 2003/02/x 1999/01/x
    19/35

    View Slide

  21. データの偏りによる違い
    最大の結果セットになりそうな user_id = 1 && regstered = 2022/05/x のパターン
    mysql> EXPLAIN SELECT COUNT(*) FROM post WHERE user_id = 1 AND registered BETWEEN '2022-05-01' AND '2022-05-01' + INTERVAL 1 M
    ONTH;
    +----+-------------+-------+------------+------+--------------------+---------+---------+-------+-------+----------+---------
    ----+
    | id | select_type | table | partitions | type | possible_keys | key | key_len | ref | rows | filtered | Extra |
    +----+-------------+-------+------------+------+--------------------+---------+---------+-------+-------+----------+---------
    ----+
    | 1 | SIMPLE | post | NULL | ref | user_id,registered | user_id | 4 | const | 49937 | 50.00 | Using where |
    +----+-------------+-------+------------+------+--------------------+---------+---------+-------+-------+----------+---------
    ----+
    mysql> EXPLAIN SELECT * FROM post WHERE user_id = 1 AND registered BETWEEN '2022-05-01' AND '2022-05-01' + INTERVAL 1 MONTH OR
    DER BY registered LIMIT 100 FOR UPDATE;
    +----+-------------+-------+------------+-------+--------------------+------------+---------+------+-------+----------+------
    ------------------------------+
    | id | select_type | table | partitions | type | possible_keys | key | key_len | ref | rows | filtered | Extra |
    +----+-------------+-------+------------+-------+--------------------+------------+---------+------+-------+----------+------
    ------------------------------+
    | 1 | SIMPLE | post | NULL | range | user_id,registered | registered | 5 | NULL | 49937 | 50.00 | Using index condition; Using where |
    +----+-------------+-------+------------+-------+--------------------+------------+---------+------+-------+----------+------
    ------------------------------+
    20/35

    View Slide

  22. データの偏りによる違い
    最小の結果セットになりそうな user_id = 2 && regstered = 2003/02/x のパターン
    mysql> EXPLAIN SELECT COUNT(*) FROM post WHERE user_id = 2 AND registered BETWEEN '2003-02-01' AND '2003-02-01' + INTERVAL 1 MONTH;
    +----+-------------+-------+------------+------+--------------------+---------+---------+-------+------+----------+-------------+
    | id | select_type | table | partitions | type | possible_keys | key | key_len | ref | rows | filtered | Extra |
    +----+-------------+-------+------------+------+--------------------+---------+---------+-------+------+----------+-------------+
    | 1 | SIMPLE | post | NULL | ref | user_id,registered | user_id | 4 | const | 1 | 5.00 | Using where |
    +----+-------------+-------+------------+------+--------------------+---------+---------+-------+------+----------+-------------+
    1 row in set, 1 warning (0.00 sec)
    mysql> EXPLAIN SELECT * FROM post WHERE user_id = 2 AND registered BETWEEN '2003-02-01' AND '2003-02-01' + INTERVAL 1 MONTH ORDER BY re
    gistered LIMIT 100 FOR UPDATE;
    +----+-------------+-------+------------+------+--------------------+---------+---------+-------+------+----------+--------------------
    ---------+
    | id | select_type | table | partitions | type | possible_keys | key | key_len | ref | rows | filtered | Extra |
    +----+-------------+-------+------------+------+--------------------+---------+---------+-------+------+----------+--------------------
    ---------+
    | 1 | SIMPLE | post | NULL | ref | user_id,registered | user_id | 4 | const | 1 | 5.00 | Using where; Using filesort |
    +----+-------------+-------+------------+------+--------------------+---------+---------+-------+------+----------+--------------------
    ---------+
    1 row in set, 1 warning (0.00 sec)
    21/35

    View Slide

  23. データの偏りによる違い
    表示上の違い、表示が違っても最適化の効き方の違い
    22/35

    View Slide

  24. データの偏りによる違い
    user_id = 3 による空振り
    mysql> EXPLAIN SELECT COUNT(*) FROM post WHERE user_id = 3 AND registered BETWEEN '2022-05-01' AND '2022-05-01' + INTERVAL 1 MONTH;
    +----+-------------+-------+------------+------+--------------------+---------+---------+-------+------+----------+-------------+
    | id | select_type | table | partitions | type | possible_keys | key | key_len | ref | rows | filtered | Extra |
    +----+-------------+-------+------------+------+--------------------+---------+---------+-------+------+----------+-------------+
    | 1 | SIMPLE | post | NULL | ref | user_id,registered | user_id | 4 | const | 1 | 50.00 | Using where |
    +----+-------------+-------+------------+------+--------------------+---------+---------+-------+------+----------+-------------+
    1 row in set, 1 warning (0.00 sec)
    mysql> EXPLAIN SELECT * FROM post WHERE user_id = 3 AND registered BETWEEN '2022-05-01' AND '2022-05-01' + INTERVAL 1 MONTH ORDER BY re
    gistered LIMIT 100 FOR UPDATE;
    +----+-------------+-------+------------+------+--------------------+---------+---------+-------+------+----------+--------------------
    ---------+
    | id | select_type | table | partitions | type | possible_keys | key | key_len | ref | rows | filtered | Extra |
    +----+-------------+-------+------------+------+--------------------+---------+---------+-------+------+----------+--------------------
    ---------+
    | 1 | SIMPLE | post | NULL | ref | user_id,registered | user_id | 4 | const | 1 | 50.00 | Using where; Using filesort |
    +----+-------------+-------+------------+------+--------------------+---------+---------+-------+------+----------+--------------------
    ---------+
    1 row in set, 1 warning (0.00 sec)
    23/35

    View Slide

  25. データの偏りによる違い
    registered = 1999/01/x による空振り
    mysql> EXPLAIN SELECT COUNT(*) FROM post WHERE user_id = 1 AND registered BETWEEN '1999-01-01' AND '1999-01-01' + INTERVAL 1 MONTH;
    +----+-------------+-------+------------+-------+--------------------+------------+---------+------+------+----------+-----------------
    -------------------+
    | id | select_type | table | partitions | type | possible_keys | key | key_len | ref | rows | filtered | Extra |
    +----+-------------+-------+------------+-------+--------------------+------------+---------+------+------+----------+-----------------
    -------------------+
    | 1 | SIMPLE | post | NULL | range | user_id,registered | registered | 5 | NULL | 1 | 50.00 | Using index condition; Using where |
    +----+-------------+-------+------------+-------+--------------------+------------+---------+------+------+----------+-----------------
    -------------------+
    1 row in set, 1 warning (0.00 sec)
    mysql> EXPLAIN SELECT * FROM post WHERE user_id = 1 AND registered BETWEEN '1999-01-01' AND '1999-01-01' + INTERVAL 1 MONTH ORDER BY re
    gistered LIMIT 100 FOR UPDATE;
    +----+-------------+-------+------------+-------+--------------------+------------+---------+------+------+----------+-----------------
    -------------------+
    | id | select_type | table | partitions | type | possible_keys | key | key_len | ref | rows | filtered | Extra |
    +----+-------------+-------+------------+-------+--------------------+------------+---------+------+------+----------+-----------------
    -------------------+
    | 1 | SIMPLE | post | NULL | range | user_id,registered | registered | 5 | NULL | 1 | 50.00 | Using index condition; Using where |
    +----+-------------+-------+------------+-------+--------------------+------------+---------+------+------+----------+-----------------
    -------------------+
    1 row in set, 1 warning (0.00 sec)
    24/35

    View Slide

  26. データの偏りによる違い
    これらの空振りは全部遅くはならないパターン(特に「存在しない値でやる」と遅くなるパターンもある。
    よく観測するのはLEFT JOIN)
    空振りFOR UPDATEは想定よりロック範囲が広くなりがちなので、どのインデックスを使ってロックされるかは重要は重


    25/35

    View Slide

  27. データの偏りによる違い
    MySQLとインデックスと私
    JOINだともっと顕著に出てきたりする

    26/35

    View Slide

  28. データの偏りによる違い
    MySQLとインデックスと私
    空振りロックのネクストキーロック

    27/35

    View Slide

  29. 偏ったように見えないデータも
    mysql> SHOW CREATE TABLE user\G
    *************************** 1. row ***************************
    Table: user
    Create Table: CREATE TABLE `user` (
    `user_id` int NOT NULL,
    `unique_identifier` varchar(32) COLLATE utf8mb4_ja_0900_as_cs NOT NULL,
    `registered` datetime NOT NULL,
    PRIMARY KEY (`user_id`),
    UNIQUE KEY `unique_identifier` (`unique_identifier`)
    ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_ja_0900_as_cs
    1 row in set (0.01 sec)
    mysql> SELECT * FROM user;
    +---------+-------------------+---------------------+
    | user_id | unique_identifier | registered |
    +---------+-------------------+---------------------+
    | 1 | yoku0825 | 2022-05-06 23:51:13 |
    | 2 | yoku0826 | 2022-05-06 23:51:19 |
    +---------+-------------------+---------------------+
    2 rows in set (0.00 sec)
    28/35

    View Slide

  30. 偏ったように見えないデータも
    mysql> EXPLAIN DELETE FROM user WHERE user_id = 1;
    +----+-------------+-------+------------+-------+---------------+---------+---------+-------+------+----------+-------------+
    | id | select_type | table | partitions | type | possible_keys | key | key_len | ref | rows | filtered | Extra |
    +----+-------------+-------+------------+-------+---------------+---------+---------+-------+------+----------+-------------+
    | 1 | DELETE | user | NULL | range | PRIMARY | PRIMARY | 4 | const | 1 | 100.00 | Using where |
    +----+-------------+-------+------------+-------+---------------+---------+---------+-------+------+----------+-------------+
    1 row in set, 1 warning (0.00 sec)
    mysql> DELETE FROM user WHERE user_id = 1;
    Query OK, 1 row affected (0.80 sec)
    mysql> EXPLAIN DELETE FROM user WHERE user_id = 2;
    +----+-------------+-------+------------+-------+---------------+---------+---------+-------+------+----------+-------------+
    | id | select_type | table | partitions | type | possible_keys | key | key_len | ref | rows | filtered | Extra |
    +----+-------------+-------+------------+-------+---------------+---------+---------+-------+------+----------+-------------+
    | 1 | DELETE | user | NULL | range | PRIMARY | PRIMARY | 4 | const | 1 | 100.00 | Using where |
    +----+-------------+-------+------------+-------+---------------+---------+---------+-------+------+----------+-------------+
    1 row in set, 1 warning (0.00 sec)
    mysql> DELETE FROM user WHERE user_id = 2;
    Query OK, 1 row affected (0.00 sec)
    29/35

    View Slide

  31. 偏ったように見えないデータ
    CASCADE や SET NULL な外部キー制約
    FKは基本的に1対多で動くので、「見えない子側の偏り」が発生しがち

    UPDATE Trigger, DELETE Trigger
    1対1なら問題ないけど、1対多(トリガーがあるのが1側)だと外部キー制約と同じようなパターンがある

    多対1(トリガーがあるのが多側)だとこの問題には当たらないけれど、1側にロックが集中するのでまた別の問題は
    ある

    30/35

    View Slide

  32. ちょっと別口の偏り
    SELECT COUNT(*) FROM post WHERE user_id = ? AND registered BETWEEN ? AND ? + INTERVAL 1
    MONTH;
    SELECT * FROM post WHERE user_id = ? AND registered BETWEEN ? AND ? + INTERVAL 1 MONTH O
    RDER BY registered LIMIT ? FOR UPDATE;
    DELETE FROM post WHERE post_id IN (?, ?, .., ?);
    31/35

    View Slide

  33. ちょっと別口の偏り(1)
    LIMITの値が 100 → 10000 で実行計画は変わることがある
    mysql> EXPLAIN SELECT * FROM post WHERE user_id = 1 AND registered BETWEEN '2022-05-01' AND '2022-05-01' + INTERVAL 1 MONTH ORDER BY registere
    d LIMIT 100 FOR UPDATE;
    +----+-------------+-------+------------+-------+--------------------+------------+---------+------+-------+----------+---------------------
    ---------------+
    | id | select_type | table | partitions | type | possible_keys | key | key_len | ref | rows | filtered | Extra |
    +----+-------------+-------+------------+-------+--------------------+------------+---------+------+-------+----------+---------------------
    ---------------+
    | 1 | SIMPLE | post | NULL | range | user_id,registered | registered | 5 | NULL | 49938 | 50.00 | Using index condition; Using where |
    +----+-------------+-------+------------+-------+--------------------+------------+---------+------+-------+----------+---------------------
    ---------------+
    1 row in set, 1 warning (0.00 sec)
    mysql> EXPLAIN SELECT * FROM post WHERE user_id = 1 AND registered BETWEEN '2022-05-01' AND '2022-05-01' + INTERVAL 1 MONTH ORDER BY registere
    d LIMIT 10000 FOR UPDATE;
    +----+-------------+-------+------------+------+--------------------+---------+---------+-------+-------+----------+------------------------
    -----+
    | id | select_type | table | partitions | type | possible_keys | key | key_len | ref | rows | filtered | Extra |
    +----+-------------+-------+------------+------+--------------------+---------+---------+-------+-------+----------+------------------------
    -----+
    | 1 | SIMPLE | post | NULL | ref | user_id,registered | user_id | 4 | const | 49938 | 50.00 | Using where; Using filesort |
    +----+-------------+-------+------------+------+--------------------+---------+---------+-------+-------+----------+------------------------
    -----+
    1 row in set, 1 warning (0.00 sec)
    32/35

    View Slide

  34. ちょっと別口の偏り(2)
    WHERE .. IN .. の要素の数で実行計画は変わることがある
    mysql> DELETE FROM post WHERE post_id IN (?, ?, ?, .., 15477);
    +----+-------------+-------+------------+-------+---------------+---------+---------+------+-------+----------+-------------+
    | id | select_type | table | partitions | type | possible_keys | key | key_len | ref | rows | filtered | Extra |
    +----+-------------+-------+------------+-------+---------------+---------+---------+------+-------+----------+-------------+
    | 1 | SIMPLE | post | NULL | range | PRIMARY | PRIMARY | 4 | NULL | 15747 | 100.00 | Using where |
    +----+-------------+-------+------------+-------+---------------+---------+---------+------+-------+----------+-------------+
    1 row in set, 1 warning (0.06 sec)
    mysql> DELETE FROM post WHERE post_id IN (?, ?, ?, .., 15478);
    +----+-------------+-------+------------+------+---------------+------+---------+------+-------+----------+-------------+
    | id | select_type | table | partitions | type | possible_keys | key | key_len | ref | rows | filtered | Extra |
    +----+-------------+-------+------------+------+---------------+------+---------+------+-------+----------+-------------+
    | 1 | DELETE | post | NULL | ALL | NULL | NULL | NULL | NULL | 99877 | 100.00 | Using where |
    +----+-------------+-------+------------+------+---------------+------+---------+------+-------+----------+-------------+
    1 row in set, 2 warnings (0.06 sec)
    | Warning | 3170 | Memory capacity of 8388608 bytes for 'range_optimizer_max_mem_size' exceeded. Range optimization was not done for this query. |
    33/35

    View Slide

  35. まとめ
    実際問題、データは偏る
    ライトユーザー vs. ヘビーユーザー

    偏ったデータが本番環境に及ぼす影響を把握する
    そもそもベストな状況は何なのかをある程度認識しないといけない

    最大の結果セット、最小の結果セット、あり得ない(はずの)結果セットで比較する

    LIMITの値、INの数も最大と最小を比較するのが吉

    せっかく本番相当のデータを用意しても「よくあるケース」だけのテストでは性能テストにならない

    34/35

    View Slide

  36. Any Questions
    and/or
    Suggestions?
    35/35

    View Slide