経緯
"完了"・"着手中"・"未着手"という3つのステータスをテーブルに設定し、いずれかのステータスを検索して絞ったテーブルを表示をするプログラムを作っておりました。
データ量が多くなった時の検索時間を短くするために、ステータスカラムにIndexを付与したのですが、どう検索してもフルスキャンになってしまいました。
結論
検索対象のデータ量に対し、約10%以下まで絞り込めるのであれば、IndexScanされる。
検索結果のデータ量が多すぎると、SeqScanになる。
・他にもIndexScanの条件はありますが、それだけで記事になると思います。
もし同じ現象で悩まれている方の、1つの参考になればと思います。
私が今回の問題にあたり、参考にさせて頂きましたリンク先を末尾に記載します。
IndexScanになるパターンとSeqScanになるパターンの実験
◆10,000データ用意
Stateカラム
“完了” = 1,000件
“着手中” = 2,000件
“未着手” = 7,000件
◆実施結果
“完了” → Index Scan
“着手中” → Seq Scan
“未着手” → Seq Scan
◆explainメソッドにて実行計画の確認
・完全一致と前方一致以外だと、IndexScanがされないそうなので、今回は完全一致にて実験しました。
今回はRansakというGemを使って、検索を行っています。
”完了”で検索(1,000 / 10,000件)
[1] pry(#<TasksController>)> @q.result(distinct: true).explain
Task Load (3.8ms) SELECT DISTINCT "tasks".* FROM "tasks" WHERE "tasks"."state" = '完了'
↳ (pry):1
=> EXPLAIN for: SELECT DISTINCT "tasks".* FROM "tasks" WHERE "tasks"."state" = '完了'
QUERY PLAN
-----------------------------------------------------------------------------------------------
Unique (cost=4.31..4.33 rows=1 width=71)
-> Sort (cost=4.31..4.32 rows=1 width=71)
Sort Key: id, subject, content, created_at, updated_at, expired_at
-> Index Scan using index_tasks_on_state on tasks (cost=0.29..4.30 rows=1 width=71)
Index Cond: ((state)::text = '完了'::text)
(5 rows)
”着手中”で検索(2,000 / 10,000件)
[1] pry(#<TasksController>)> @q.result(distinct: true).explain
Task Load (9.3ms) SELECT DISTINCT "tasks".* FROM "tasks" WHERE "tasks"."state" = '着手中'
↳ (pry):2
=> EXPLAIN for: SELECT DISTINCT "tasks".* FROM "tasks" WHERE "tasks"."state" = '着手中'
QUERY PLAN
------------------------------------------------------------------------------
HashAggregate (cost=305.00..325.00 rows=2000 width=70)
Group Key: id, subject, content, created_at, updated_at, expired_at, state
-> Seq Scan on tasks (cost=0.00..270.00 rows=2000 width=70)
Filter: ((state)::text = '着手中'::text)
(4 rows)
”未着手”で検索(7,000 / 10,000件)
[1] pry(#<TasksController>)> @q.result(distinct: true).explain
Task Load (21.2ms) SELECT DISTINCT "tasks".* FROM "tasks" WHERE "tasks"."state" = '未着手'
↳ (pry):3
=> EXPLAIN for: SELECT DISTINCT "tasks".* FROM "tasks" WHERE "tasks"."state" = '未着手'
QUERY PLAN
------------------------------------------------------------------------------
HashAggregate (cost=392.50..462.50 rows=7000 width=70)
Group Key: id, subject, content, created_at, updated_at, expired_at, state
-> Seq Scan on tasks (cost=0.00..270.00 rows=7000 width=70)
Filter: ((state)::text = '未着手'::text)
(4 rows)
参考リンク
【PostgreSQL】インデックス必要?explainで確認できない
https://qiita.com/gijutu/items/45c488372714eeaf6157
Railsエンジニアなら知っておきたい!RDBでindex作成を検討するときってどんなとき?
https://qiita.com/viptakechan/items/03cb15684fdace0b5bbe