Ich habe eine Tabelle progresses(enthält derzeit Hunderttausende von Datensätzen):
Column | Type | Modifiers
---------------+-----------------------------+---------------------------------------------------------
id | integer | not null default nextval('progresses_id_seq'::regclass)
lesson_id | integer |
user_id | integer |
created_at | timestamp without time zone |
deleted_at | timestamp without time zone |
Indexes:
"progresses_pkey" PRIMARY KEY, btree (id)
"index_progresses_on_deleted_at" btree (deleted_at)
"index_progresses_on_lesson_id" btree (lesson_id)
"index_progresses_on_user_id" btree (user_id)
und eine Ansicht, v_latest_progressesdie nach den neuesten progressvon user_idund fragt lesson_id:
SELECT DISTINCT ON (progresses.user_id, progresses.lesson_id)
progresses.id AS progress_id,
progresses.lesson_id,
progresses.user_id,
progresses.created_at,
progresses.deleted_at
FROM progresses
WHERE progresses.deleted_at IS NULL
ORDER BY progresses.user_id, progresses.lesson_id, progresses.created_at DESC;
Ein Benutzer kann für eine bestimmte Lektion viele Fortschritte erzielen. Wir möchten jedoch häufig eine Reihe der zuletzt erstellten Fortschritte für eine bestimmte Gruppe von Benutzern oder Lektionen (oder eine Kombination aus beiden) abfragen.
Die Ansicht v_latest_progressesmacht das gut und ist sogar performant, wenn ich eine Reihe von user_ids spezifiziere :
# EXPLAIN SELECT "v_latest_progresses".* FROM "v_latest_progresses" WHERE "v_latest_progresses"."user_id" IN ([the same list of ids given by the subquery in the second example below]);
QUERY PLAN
----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
Unique (cost=526.68..528.66 rows=36 width=57)
-> Sort (cost=526.68..527.34 rows=265 width=57)
Sort Key: progresses.user_id, progresses.lesson_id, progresses.created_at
-> Index Scan using index_progresses_on_user_id on progresses (cost=0.47..516.01 rows=265 width=57)
Index Cond: (user_id = ANY ('{ [the above list of user ids] }'::integer[]))
Filter: (deleted_at IS NULL)
(6 rows)
Wenn ich jedoch versuche, dieselbe Abfrage user_iddurchzuführen und die Menge von s durch eine Unterabfrage zu ersetzen, wird dies sehr ineffizient:
# EXPLAIN SELECT "v_latest_progresses".* FROM "v_latest_progresses" WHERE "v_latest_progresses"."user_id" IN (SELECT "users"."id" FROM "users" WHERE "users"."company_id"=44);
QUERY PLAN
-----------------------------------------------------------------------------------------------------
Merge Semi Join (cost=69879.08..72636.12 rows=19984 width=57)
Merge Cond: (progresses.user_id = users.id)
-> Unique (cost=69843.45..72100.80 rows=39969 width=57)
-> Sort (cost=69843.45..70595.90 rows=300980 width=57)
Sort Key: progresses.user_id, progresses.lesson_id, progresses.created_at
-> Seq Scan on progresses (cost=0.00..31136.31 rows=300980 width=57)
Filter: (deleted_at IS NULL)
-> Sort (cost=35.63..35.66 rows=10 width=4)
Sort Key: users.id
-> Index Scan using index_users_on_company_id on users (cost=0.42..35.46 rows=10 width=4)
Index Cond: (company_id = 44)
(11 rows)
Ich versuche herauszufinden, warum PostgreSQL die DISTINCTAbfrage für die gesamte progressesTabelle ausführen möchte, bevor sie im zweiten Beispiel nach der Unterabfrage gefiltert wird.
Hätte jemand einen Rat, wie man diese Abfrage verbessern kann?
144.07..144.6unter den 70.000, die ich bekommen habe! Vielen Dank.