LoginSignup
0
0

More than 5 years have passed since last update.

explainの大切さを知った時のメモ

Posted at

「インデックスを使うとなんか早くなるよ」ぐらいの知識しかない自分が
高速化のためにexplainして確認する大切さを身を持って味わう経験ができました。
後の役に立つと思ってメモを残しておきます。

こんなデータがあるとする。

chuumonsテーブル

id date kyaku_id
1 2018-01-01 00:00:00 1
2 2018-01-02 00:00:00 1
3 2018-01-01 10:00:00 2
  • dateは注文発生日、kyaku_idは注文した客のidを表す
  • dateは秒単位で指定されるので、重複がかなり少ない。

meisaisテーブル

id chuumon_id shouhin_id
1 1 1
2 1 2
3 2 1
  • レコード1つがある注文の商品単位での明細を表す
  • 一度の注文で2種類の商品を買った場合、おなじcuumon_idのレコードが2つできる
  • shouhin_idは買った商品を表す

やりたいこと
これらのテーブルから以下の条件で注文を抽出したい
・発生時点で商品id=1を3回購入している

これを実現するためにこんなSQLを書いてみた。

SELECT
    chuumons_narrowed_by_item.id
FROM
    chuumons AS chuumons_narrowed_by_item
INNER JOIN
    chuumons AS onaji_kyaku_chuumons
ON
    chuumons_narrowed_by_item.kyaku_id = same_user_chuumons.kyaku_id
    AND
    onaji_kyaku_chuumons.date <= chuumons_narrowed_by_item.date
INNER JOIN
    meisais
ON
    same_user_chuumons.id = meisais.chuumon_id
AND
    meisais.item_id = 1
GROUP BY
    chuumons_narrowed_by_item.id
HAVING
    COUNT(chuumon_narrowed_by_item.id) = 3

注文ごとに同じ客の過去注文をjoinして、item_idごとに集計する。

これで仕様は満たせるのですが
実際にこのクエリを動かすとかなり時間がかかってしまいました。

原因としては、
chuumonsやmeisaisのレコード数がものすごく多い場合、
これらがJOINされまくることによってとてつもないレコード数になってしまう。
そのレコード一つ一つをCOUNTで数え上げることになるので、
計算量も相当なものになってしまう、ということのようです。

というわけで対策としては

  • shouhin_idよりも絞り込みの効果が大きいカラムで、ベースになるchuumonテーブルを絞り込む ということが考えられます。 そうすればJOINしてもレコード数はさほど増えないはず。

テーブルの説明でちらっと述べましたが、
chuumonsテーブルには重複が少ないdateというカラムがあります。これを条件に追加してみます。

SELECT
    chuumons_narrowed_by_item.id
FROM
    chuumons AS chuumons_narrowed_by_item
INNER JOIN
    chuumons AS onaji_kyaku_chuumons
ON
    chuumons_narrowed_by_item.kyaku_id = same_user_chuumons.kyaku_id
    AND
    onaji_kyaku_chuumons.date <= chuumons_narrowed_by_item.date
    AND
    chuumons_narrowed_by_item.date BETWEEN '2018-01-01' AND '2018-01-31'
INNER JOIN
    meisais
ON
    same_user_chuumons.id = meisais.chuumon_id
AND
    meisais.item_id = 1
GROUP BY
    chuumons_narrowed_by_item.id
HAVING
    COUNT(chuumon_narrowed_by_item.id) = 3

この改修によって本当に高速化は出来たのですが、すこし気になる現象が起きました。

データ量によって高速化され具合が違う

どの程度効果があったか調べるために、chuumonsのレコード数を変えて試してみました。
(meisaisはchuumonsの約10倍の数があるとする)

chuumonsレコード数 改修前にかかった時間(s) 改修後にかかった時間(s)
20万 10 1
100万 30 27

改修によって速くなってはいるのですが、レコード数が多いとあまり改修の効果が高くないのです
これはなぜなのか、explainして調べてみました

  • explain結果 レコード数20万の場合(一部省略)

    id select_type table type possible_keys key key_len
    1 SIMPLE chuumons_narrowed_by_item range PRIMARY, id, date, kyaku_id date 5
    1 SIMPLE onaji_kyaku_chuumons ref PRIMARY, id, date, kyaku_id kyaku_id 4
    1 SIMPLE meisais ref chuumon_id, shouhin_id chuumon_id 4
  • explain結果 レコード数100万の場合(一部省略)

    id select_type table type possible_keys key key_len
    1 SIMPLE meisais ref chuumon_id, shouhin_id shouhin_id 4
    1 SIMPLE onaji_kyaku_chuumons eq_ref PRIMARY, id, kyaku_id PRIMARY 4
    1 SIMPLE chuumons_narrowed_by_item ref PRIMARY, id, date, kyaku_id kyaku_id 4

結果を出力するにあたって、参照された順番が早いものほど上に表示されます。
なので今回の場合、レコード数20万のときはchuumonsテーブルから、
100万のときはmeisaiテーブルから参照したという違いが見られます。

また、possible_keysとkeysにも違いがあります。
これらはそれぞれ「テーブルの探索に使用可能なキー」「実際に使用したキー」を表しています。
レコード数20万のときは最初にchuumonsテーブルをdateで絞り込んだことがわかります。

つまり、JOINする段階でchuumonsテーブルのレコード数をぐっと小さくすることに成功しているため、
dateの条件を付加した恩恵が大きかったということになります。

なぜこのような違いが現れたのか

MySQLはテーブルを探索する際、どのキーで絞り込むかを
統計情報に基づいて判断しているそうです。

テーブルのレコード数も、どのキーが有効か判断する手がかりとして使われている、ということみたいですね。

今回はMySQLが「テーブル数が100万件あるなら、はじめにこのテーブルをこのキーで絞り込みしよう」と判定した結果、あまり有効でないキーが選択されてしまったようです。

確実に高速化するために

今回の場合、chuumons.dateにインデックスを付加することで
レコード数に関わらず「はじめにchuumonsテーブルをdateで絞り込む」よう動作させることができるようになりました。

インデックスの付いていない日付型のカラムは絞り込みに使用する優先度が低い、ということなのかもしれません。

0
0
2

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
0
0