17. index の利用
$ Job.where(id: 1).explain
=> EXPLAIN for: SELECT "jobs".* FROM "jobs" WHERE "jobs"."id" = $1 [["id", 1]]
QUERY PLAN
-----------------------------------------------------------------------
Index Scan using jobs_pkey on jobs (cost=0.29..8.31 rows=1 width=28)
Index Cond: (id = 1)
$ Job.where(id_without_index: 1).explain
=> EXPLAIN for: SELECT "jobs".* FROM "jobs" WHERE "jobs"."id_without_index"
= $1 [["id_without_index", 1]]
QUERY PLAN
--------------------------------------------------------
Seq Scan on jobs (cost=0.00..1022.00 rows=1 width=28)
Filter: (id_without_index = 1)
index有り
index無し
(Seq Scan)
18. index バッドパターン その1
「index を貼ったカラムに演算」
$ Profile.where('lower(email) = ?', 'minami@wantedly.com').limit(1).explain
=> EXPLAIN for: SELECT "profiles".* FROM "profiles"
WHERE (lower(email) = 'minami@wantedly.com') LIMIT 1
QUERY PLAN
------------------------------------------------------------------
Limit (cost=0.00..5.08 rows=1 width=54)
-> Seq Scan on profiles (cost=0.00..254.00 rows=50 width=54)
Filter: (lower(email) = 'minami@wantedly.com'::text)
index は key の比較で sort してるので、
演算が行われると利用できない
「クエリ書き換え」 or 「Indexes on Expression を利用」
19. index バッドパターン その2
「絞り込み条件の緩いWHERE」
$ Profile.where(gender: ‘female').explain
=> EXPLAIN for: SELECT "profiles".* FROM "profiles" WHERE
"profiles"."gender" = $1 [["gender", "female"]]
QUERY PLAN
--------------------------------------------------------------
Seq Scan on profiles (cost=0.00..229.00 rows=5038 width=54)
Filter: (gender = 'female'::text)
male female
profiles.gender の分布
デフォルトだと、 4分の1以下に絞り込まれる必要あり
32. 2. Multicolumn Indexes
先頭の要素の index としても効く
より詳細を知りたい方は:
http://www.postgresql.org/docs/current/static/indexes-multicolumn.html
$ TouristSpot.where(country: 'japan').explain
=> EXPLAIN for: SELECT "tourist_spots".* FROM "tourist_spots" WHERE "tourist_spots"."country" = $1
[["country", "japan"]]
QUERY PLAN
-------------------------------------------------------------------------------------------------------
Bitmap Heap Scan on tourist_spots (cost=4.50..41.67 rows=10 width=52)
Recheck Cond: (country = 'japan'::text)
-> Bitmap Index Scan on index_tourist_spots_on_country_and_city (cost=0.00..4.49 rows=10 width=0)
Index Cond: (country = 'japan'::text)
33. 3. Indexes on Expressions
関数などの返り値を key として index を作る事ができる
# db/migrate/db/migrate/20151210065304_add_indexes_on_~.rb
def up
execute <<-SQL
CREATE INDEX index_profiles_with_indexes_on_expressions_on_lower_email
ON profiles_with_indexes_on_expressions(lower(email));
SQL
end
def down
execute <<-SQL
DROP INDEX index_profiles_with_indexes_on_expressions_on_lower_email
SQL
end
34. 3. Indexes on Expressions
lower(email) を index として利用
詳細はこちら:
http://www.postgresql.org/docs/current/static/indexes-expressional.html
$ ProfilesWithIndexesOnExpression.where("lower(email) = 'minami@wantedly.com'").explain
=> EXPLAIN for: SELECT "profiles_with_indexes_on_expressions".* FROM
"profiles_with_indexes_on_expressions" WHERE (lower(email) = 'minami@wantedly.com')
QUERY PLAN
------------------------------------------------------------------------------------------------------
Index Scan using index_profiles_with_indexes_on_expressions_on_lower_email on
profiles_with_indexes_on_expressions (cost=0.29..8.30 rows=1 width=48)
Index Cond: (lower(email) = 'minami@wantedly.com'::text)
35. HashAggregate
Hash Join
Seq ScanHash
Index Scan
次のステップはデータの結合(JOIN)
$ Job.joins(:company).group('companies.country').where('companies.id < 1000’)
.select('companies.country', 'COUNT(jobs.id)').explain
=> EXPLAIN for: SELECT companies.country, COUNT(jobs.id) FROM "jobs" INNER JOIN "companies" ON
"companies"."id" = "jobs"."company_id" WHERE (companies.id < 1000) GROUP BY companies.country
47. $ Job.joins(:company).group('companies.country').where('companies.id < 1000’)
.select('companies.country', 'COUNT(jobs.id)').explain
=> EXPLAIN for: SELECT companies.country, COUNT(jobs.id) FROM "jobs" INNER JOIN "companies" ON
"companies"."id" = "jobs"."company_id" WHERE (companies.id < 1000) GROUP BY companies.country
QUERY PLAN
-------------------------------------------------------------------------------------------------------
HashAggregate (cost=1213.79..1220.12 rows=634 width=16)
-> Hash Join (cost=54.28..1188.79 rows=5000 width=16)
Hash Cond: (jobs.company_id = companies.id)
-> Seq Scan on jobs (cost=0.00..897.00 rows=50000 width=8)
-> Hash (cost=41.78..41.78 rows=1000 width=16)
-> Index Scan using companies_pkey on companies (cost=0.29..41.78 rows=1000 width=16)
Index Cond: (id < 1000)
2. Hash Aggregate
グループキーを key とする、一時的な Hash Tableを作成
48. ORDER BY を指定する事で、 Sort 処理が入る
ラストステップが Sort と Limitの場合
$ PageViewLog.order(:viewed_at).limit(20).explain
=> EXPLAIN for: SELECT "page_view_logs".* FROM "page_view_logs"
ORDER BY "page_view_logs"."viewed_at" ASC LIMIT 20
QUERY PLAN
-----------------------------------------------------------------------------------
Limit (cost=22026.31..22026.36 rows=20 width=28)
-> Sort (cost=22026.31..23278.87 rows=501024 width=28)
Sort Key: viewed_at
-> Seq Scan on page_view_logs (cost=0.00..8694.24 rows=501024 width=28)
Disk sort になると、すごく遅い
49. ORDER BY には index
index があればすでに sort 済みなので、sort 処理が不要
$PageViewLogWithIndex.order(:viewed_at).limit(20).explain
=> EXPLAIN for: SELECT "page_view_log_with_indices".* FROM "page_view_log_with_indices"
ORDER BY "page_view_log_with_indices"."viewed_at" ASC LIMIT 20
QUERY PLAN
---------------------------------------------------------------------------------------------------
Limit (cost=0.42..1.09 rows=20 width=28)
-> Index Scan using index_page_view_log_with_indices_on_viewed_at on page_view_log_with_indices
(cost=0.42..16698.78 rows=501024 width=28)
51. 1. Window Functions
http://www.postgresql.org/docs/current/static/tutorial-window.html
$ Company.select('country, rank() OVER (PARTITION BY country ORDER BY id DESC)').explain
=> EXPLAIN for: SELECT country, rank() OVER (PARTITION BY country ORDER BY id DESC) FROM
"companies"
QUERY PLAN
----------------------------------------------------------------------------
WindowAgg (cost=936.35..1155.63 rows=10964 width=16)
-> Sort (cost=936.35..963.76 rows=10964 width=16)
Sort Key: country, id
-> Seq Scan on companies (cost=0.00..200.64 rows=10964 width=16)
Partition ごとに、値を計算
country | rank
--------------+------
britain | 1
china | 1
china | 2
china | 3
country_0 | 1
高機能な集約関数
52. 2. Json Type
Json データを保存可能
ActiveREcord で対応済み
$ Event.create(payload: { kind: "user_renamed", change: ["jack", "john"]})
(0.1ms) BEGIN
SQL (1.7ms) INSERT INTO "events" ("payload", "created_at", "updated_at") VALUES ($1, $2, $3)
RETURNING "id" [["payload", "{"kind":"user_renamed","change":["jack","john"]}"],
["created_at", "2015-12-10 09:57:52.294809"], ["updated_at", "2015-12-10 09:57:52.294809"]]
(0.4ms) COMMIT
# db/migrate/~.rb
def change
create_table :events do |t|
t.json :payload
end
end
53. 2. Json Type
http://www.postgresql.org/docs/current/static/functions-json.html
Json の値取得用の operator が存在
$ Event.where("payload->>'name' = ?", "test1").explain
=> EXPLAIN for: SELECT "events".* FROM "events" WHERE (payload->>'name' = 'test1')
QUERY PLAN
--------------------------------------------------------
Seq Scan on events (cost=0.00..24.85 rows=5 width=52)
Filter: ((payload ->> 'name'::text) = 'test1'::text)