🔧
📝【Rails】クエリ最適化のためのexplainメソッド
explainメソッド
explainメソッドって何それ、おいしいの?
Active Record でのSQLクエリの実行計画を提供してくれるもの。クエリのパフォーマンスを理解し、最適化を求める人にとっては非常に美味しいメソッド
explainメソッドの実装コード ※1
explain
メソッド内ではSQL文の生成や実行時間の計測、生成したSQL文によるクエリの実行、実行結果の整形処理を行っている
def explain(arel, binds = [])
sql = "EXPLAIN #{to_sql(arel, binds)}" # 生成
start = Time.now
result = exec_query(sql, "EXPLAIN", binds) # 実行
elapsed = Time.now - start # 計測
MySQL::ExplainPrettyPrinter.new.pp(result, elapsed) # 整形
end
使用方法
対象となるクエリチェーンの最後に追加する
User.where(name: 'Bob').explain
出力結果例(MySQL ※2)
// 上記のクエリの実行結果ではありません
+----+-------------+--------------------------+------------+--------+-----------------------------------------------------------------------------------------+--------------------------------------------+---------+--------------------------------------------------------------+------+----------+------------------------------------+
| id | select_type | table | partitions | type | possible_keys | key | key_len | ref | rows | filtered | Extra |
+----+-------------+--------------------------+------------+--------+-----------------------------------------------------------------------------------------+--------------------------------------------+---------+--------------------------------------------------------------+------+----------+------------------------------------+
| 1 | SIMPLE | exhibit_patrol_histories | NULL | range | exhibit_patrol_histories_admin_user_id_index,exhibit_patrol_histories_patroled_at_index | exhibit_patrol_histories_patroled_at_index | 6 | NULL | 1 | 100.0 | Using index condition; Using where |
| 1 | SIMPLE | admin_users | NULL | eq_ref | PRIMARY | PRIMARY | 8 | GameTrade_development.exhibit_patrol_histories.admin_user_id | 1 | 12.5 | Using where |
+----+-------------+--------------------------+------------+--------+-----------------------------------------------------------------------------------------+--------------------------------------------+---------+--------------------------------------------------------------+------+----------+------------------------------------+
2 rows in set (0.00 sec)
※2 explain
メソッドによる出力はデータベースごとに異なる
各フィールドの読み方(重要なものだけを表示 ※3)
type
対象のテーブルに対してどのような方法でアクセスするか
-
const
- PRIMARY KEY または UNIQUE インデックスによる単一行の参照で、最も効率が良い
- PRIMARY KEY や UNIQUE インデックスに基づいて特定の値を指定するクエリで使用されることがある
-
eq_ref
-
JOIN
で PRIMARY KEY または UNIQUE KEY を利用した一つの行の参照 - テーブル同士の一対一の結合で使用されることがある
-
-
ref
- インデックスを使用して等価検索(
WHERE key = value
)を行い、複数の行が存在する可能性のある行を選択 - 外部キーがある場合や、一意でないインデックスを持つカラムでの検索クエリで使用されることがある
- 検索条件を調整することで一致する行の数を減らし、クエリの効率を改善することが可能
- インデックスを使用して等価検索(
-
range
- インデックスを使用して特定の範囲検索
-
BETWEEN
や<
、>
などの演算子を使用しているクエリで使用されることがある - フルテーブルスキャンよりも効率的であることが多いが、範囲が広すぎる場合はパフォーマンスに影響を与える可能性がある
-
ALL
- フルテーブルスキャン。インデックスが全く利用されていない
- 一般的に効率が悪く、小さいテーブルでない限りパフォーマンスに影響を及ぼす可能性がある
possible_keys
使用可能なインデックスの候補リスト
key
実際に使用するインデックス(NULL
はインデックスが使用されていないことを意味する)
Extra
クエリがどのように実行されるかに関する追加情報
-
Using index
- 必要なデータが全てインデックスから直接取得され、テーブルの行自体を読み込む必要がないことを示す
-
Using where
-
WHERE
句がクエリに使用されたことを示す
-
-
Using temporary
- クエリの実行に一時テーブルが使用されていることを示す
- ソートや集計(ex.
GROUP BY
,ORDER BY
)のクエリで見られる
ref
検索条件で、インデックスのキーが参照(比較)されるカラムまたは定数。定数が指定されている場合は const 、JOINが実行されている場合には、結合先のテーブルで検索条件として利用されているカラムが表示される
※3 参考になる記事: https://nippondanji.blogspot.com/2009/03/mysqlexplain.html
explainメソッドをcountメソッドの後には実行できなかった件【余談】
結論
count
メソッドにだけ実行できなかったのではなく、クエリが発行するメソッドの後にはそもそも explain
メソッドが実行できない
サンプルコード
p Exhibit.where(sales_status: 1).count.explain
発生したエラー
undefined method `explain' for 5396:Integer
原因
.explain
の実行対象が、 .count
ですでにクエリが発行された結果(Integer)に対して実行してしまっているのでエラーが発生する
irb(main)> p Exhibit.where(sales_status: 1).count.class
Integer
Discussion