Я использую PostgreSQL 10
Вот моя модель:
https://imgur.com/bibWSq8
Каждый review
принадлежит только одному product
. Каждый product
может принадлежать многим category
с. Каждый category
может иметь только одного родителя category
.
Я использую Prisma для запроса базы данных. Это своего рода ОРМ.
Я хочу выбрать первые 10 review
s из всех product
s, которые принадлежат category
с id = 27
.
Вот запрос, сгенерированный Prisma:
select
"Alias"."id"
from "database"."review" as "Alias"
where ("Alias"."id"
in (select "database"."review"."id"
from "database"."review"
where "database"."review"."product"
in (select "database"."category_to_product"."product"
from "database"."category_to_product"
join "database"."category" as "category_product_Alias"
on "category_product_Alias"."id" = "database"."category_to_product"."category"
where ("category_product_Alias"."id" = 27
or "category_product_Alias"."id"
in (select "database"."category"."id"
from "database"."category"
join "database"."category" as "category_category_product_Alias"
on "category_category_product_Alias"."id" = "database"."category"."parent"
where "category_category_product_Alias"."id" = 27
)
)
)
)
)
order by "Alias"."id" desc
limit 11
offset 0;
Есть 1.500.000 review
с, 12.000 product
с и 130 category
с. Этот запрос занимает почти 3 секунды.
Я пытался создать индексы, но это не сработало:
CREATE UNIQUE INDEX category_pkey ON "database".category USING btree (id)
CREATE INDEX idx_category_parent ON "database".category USING btree (parent)
CREATE UNIQUE INDEX "category_to_product_AB_unique" ON "database".category_to_product USING btree (category, product)
CREATE INDEX "category_to_product_B" ON "database".category_to_product USING btree (product))
CREATE UNIQUE INDEX product_pkey ON "database".product USING btree (id)
CREATE INDEX idx_review_product ON "database".review USING btree (product)
CREATE UNIQUE INDEX review_pkey ON "database".review USING btree (id)
А вот результат при запуске explain analyze
:
Limit (cost=9.00..101.89 rows=11 width=4) (actual time=3428.508..3431.048 rows=11 loops=1)
-> Merge Semi Join (cost=9.00..12584725.82 rows=1490226 width=4) (actual time=3428.507..3431.043 rows=11 loops=1)
Merge Cond: ("Alias".id = review.id)
-> Index Only Scan Backward using review_pkey on review "Alias" (cost=0.43..84869.82 rows=1490226 width=4) (actual time=0.008..152.954 rows=1054436 loops=1)
Heap Fetches: 0
-> Nested Loop Semi Join (cost=8.57..12477502.61 rows=1490226 width=4) (actual time=3188.974..3191.303 rows=11 loops=1)
-> Index Scan Backward using review_pkey on review (cost=0.43..266561.32 rows=1490226 width=8) (actual time=0.004..415.244 rows=1054436 loops=1)
-> Nested Loop (cost=8.14..8.18 rows=1 width=4) (actual time=0.002..0.002 rows=0 loops=1054436)
-> Index Scan using "category_to_product_B" on category_to_product (cost=0.29..0.30 rows=1 width=8) (actual time=0.001..0.001 rows=1 loops=1054436)
Index Cond: (product = review.product)
-> Index Only Scan using category_pkey on category "category_product_Alias" (cost=7.86..7.88 rows=1 width=4) (actual time=0.001..0.001 rows=0 loops=1084175)
Index Cond: (id = category_to_product.category)
Filter: ((id = 27) OR (hashed SubPlan 1))
Rows Removed by Filter: 1
Heap Fetches: 0
SubPlan 1
-> Nested Loop (cost=0.00..7.71 rows=1 width=4) (actual time=0.016..0.016 rows=0 loops=1)
-> Seq Scan on category (cost=0.00..3.85 rows=1 width=8) (actual time=0.015..0.016 rows=0 loops=1)
Filter: (parent = 27)
Rows Removed by Filter: 148
-> Seq Scan on category "category_category_product_Alias" (cost=0.00..3.85 rows=1 width=4) (never executed)
Filter: (id = 27)
Planning time: 0.649 ms
Execution time: 3431.098 ms
Я думаю, что мои данные не слишком велики, но запрос слишком медленный. Есть ли способ сделать это быстрее?
Обновление 1
Я просто делаю путь @Laurenz Albe, это быстрее. Вот результат
Limit (cost=217773.56..217773.59 rows=11 width=8) (actual time=735.033..735.041 rows=11 loops=1)
-> Sort (cost=217773.56..221499.13 rows=1490226 width=8) (actual time=735.031..735.033 rows=11 loops=1)
Sort Key: (("Alias".id + 0)) DESC
Sort Method: top-N heapsort Memory: 25kB
-> Hash Semi Join (cost=99929.33..184545.76 rows=1490226 width=8) (actual time=354.030..733.405 rows=13589 loops=1)
Hash Cond: ("Alias".id = review.id)
-> Seq Scan on review "Alias" (cost=0.00..60400.26 rows=1490226 width=4) (actual time=0.005..157.747 rows=1482065 loops=1)
-> Hash (cost=81301.50..81301.50 rows=1490226 width=4) (actual time=350.842..350.842 rows=13589 loops=1)
Buckets: 2097152 Batches: 1 Memory Usage: 16862kB
-> Hash Join (cost=410.63..81301.50 rows=1490226 width=4) (actual time=3.363..347.392 rows=13589 loops=1)
Hash Cond: (review.product = category_to_product.product)
-> Seq Scan on review (cost=0.00..60400.26 rows=1490226 width=8) (actual time=0.011..144.852 rows=1482065 loops=1)
-> Hash (cost=326.86..326.86 rows=6702 width=4) (actual time=2.121..2.121 rows=100 loops=1)
Buckets: 8192 Batches: 1 Memory Usage: 68kB
-> HashAggregate (cost=259.84..326.86 rows=6702 width=4) (actual time=2.064..2.103 rows=100 loops=1)
Group Key: category_to_product.product
-> Hash Join (cost=12.86..243.08 rows=6702 width=4) (actual time=0.336..2.026 rows=100 loops=1)
Hash Cond: (category_to_product.category = "category_product_Alias".id)
-> Seq Scan on category_to_product (cost=0.00..194.03 rows=13403 width=8) (actual time=0.004..0.873 rows=12063 loops=1)
-> Hash (cost=11.93..11.93 rows=74 width=4) (actual time=0.037..0.037 rows=1 loops=1)
Buckets: 1024 Batches: 1 Memory Usage: 9kB
-> Seq Scan on category "category_product_Alias" (cost=7.71..11.93 rows=74 width=4) (actual time=0.025..0.035 rows=1 loops=1)
Filter: ((id = 27) OR (hashed SubPlan 1))
Rows Removed by Filter: 147
SubPlan 1
-> Nested Loop (cost=0.00..7.71 rows=1 width=4) (actual time=0.015..0.015 rows=0 loops=1)
-> Seq Scan on category (cost=0.00..3.85 rows=1 width=8) (actual time=0.015..0.015 rows=0 loops=1)
Filter: (parent = 27)
Rows Removed by Filter: 148
-> Seq Scan on category "category_category_product_Alias" (cost=0.00..3.85 rows=1 width=4) (never executed)
Filter: (id = 27)
Planning time: 0.591 ms
Execution time: 735.127 ms
Обновление 2
Я попытался упростить запрос:
explain analyze select
"review"."id"
from "review"
where "review"."product" in
(
select "category_to_product"."product"
from "category_to_product"
join "category"
on "category"."id" = "category_to_product"."category"
where "category"."id" = 27 or "category"."parent" = 27
)
order by "reviewty$dev"."review"."id" desc
limit 11
offset 0;
Но результат не сильно меняется
Limit (cost=0.86..456.52 rows=11 width=4) (actual time=3354.756..3357.181 rows=11 loops=1)
-> Nested Loop Semi Join (cost=0.86..1019733.07 rows=24617 width=4) (actual time=3354.754..3357.176 rows=11 loops=1)
-> Index Scan Backward using review_pkey on review (cost=0.43..266561.32 rows=1490226 width=8) (actual time=0.007..391.076 rows=1054436 loops=1)
-> Nested Loop (cost=0.43..0.50 rows=1 width=4) (actual time=0.002..0.002 rows=0 loops=1054436)
-> Index Scan using "category_to_product_B" on category_to_product (cost=0.29..0.30 rows=1 width=8) (actual time=0.001..0.001 rows=1 loops=1054436)
Index Cond: (product = review.product)
-> Index Scan using category_pkey on category (cost=0.14..0.17 rows=1 width=4) (actual time=0.001..0.001 rows=0 loops=1084175)
Index Cond: (id = category_to_product.category)
Filter: ((id = 27) OR (parent = 27))
Rows Removed by Filter: 1
Planning time: 0.434 ms
Execution time: 3357.210 ms
Единственный способ сделать это сейчас - добавить + 0
после order by "Alias"."id"
. Как ни печально, как я уже сказал, этот запрос генерируется Prisma (prisma.io), а не мной, я хочу написать собственный sql.
Обновление 3
@ Ancoron прав, set enable_nestloop = off
перед запуском мой запрос сделает это быстрее. Это заставляет PostgreSQL использовать hash join
вместо nested loop
.
Limit (cost=10000238022.63..10000238023.45 rows=11 width=4) (actual time=629.606..629.804 rows=11 loops=1)
-> Merge Semi Join (cost=10000238022.63..10000348970.97 rows=1490226 width=4) (actual time=629.605..629.797 rows=11 loops=1)
Merge Cond: ("Alias".id = review.id)
-> Index Only Scan Backward using review_pkey on review "Alias" (cost=0.43..84869.82 rows=1490226 width=4) (actual time=0.006..152.252 rows=1054436 loops=1)
Heap Fetches: 0
-> Sort (cost=10000238022.20..10000241747.77 rows=1490226 width=4) (actual time=390.996..391.000 rows=11 loops=1)
Sort Key: review.id DESC
Sort Method: quicksort Memory: 1021kB
-> Hash Semi Join (cost=10000000604.70..10000085221.14 rows=1490226 width=4) (actual time=4.306..388.164 rows=13589 loops=1)
Hash Cond: (review.product = category_to_product.product)
-> Seq Scan on review (cost=0.00..60400.26 rows=1490226 width=8) (actual time=0.004..157.976 rows=1482065 loops=1)
-> Hash (cost=10000000529.30..10000000529.30 rows=6032 width=4) (actual time=0.617..0.617 rows=100 loops=1)
Buckets: 8192 Batches: 1 Memory Usage: 68kB
-> Merge Join (cost=10000000008.29..10000000529.30 rows=6032 width=4) (actual time=0.555..0.603 rows=100 loops=1)
Merge Cond: (category_to_product.category = "category_product_Alias".id)
-> Index Only Scan using "category_to_product_AB_unique" on category_to_product (cost=0.29..419.82 rows=12063 width=8) (actual time=0.007..0.374 rows=2272 loops=1)
Heap Fetches: 1123
-> Index Only Scan using category_pkey on category "category_product_Alias" (cost=10000000007.86..10000000018.82 rows=74 width=4) (actual time=0.024..0.035 rows=1 loops=1)
Filter: ((id = 27) OR (hashed SubPlan 1))
Rows Removed by Filter: 147
Heap Fetches: 0
SubPlan 1
-> Nested Loop (cost=10000000000.00..10000000007.71 rows=1 width=4) (actual time=0.015..0.015 rows=0 loops=1)
-> Seq Scan on category (cost=0.00..3.85 rows=1 width=8) (actual time=0.015..0.015 rows=0 loops=1)
Filter: (parent = 27)
Rows Removed by Filter: 148
-> Seq Scan on category "category_category_product_Alias" (cost=0.00..3.85 rows=1 width=4) (never executed)
Filter: (id = 27)
Planning time: 0.594 ms
Execution time: 629.857 ms
Но я спрашиваю себя, почему я должен это делать, PostgreSQL выбирает неправильный план, он использует вложенный цикл вместо хеш-соединения, это замедляет мой запрос. Это зрелая база данных, поэтому я полагал, что это была моя ошибка, когда запрос медленный, я пытался создать индексы, переписать запрос в надежде, что PostgreSQL изменит свой план, но это не так. Это приемлемо? Другое дело, я уверен, что мой запрос будет выполняться быстрее в каждом случае.
Вот мой запрос Prisma:
# Write your query or mutation here
query {
reviews (where: {
product:{
categories_some: {
OR:[
{
id: 27
},
{
parent: {
id: 27
}
}
]
}
}
}, orderBy:id_DESC, first:11, skip:0){
id
}
}
Я не нашел другого способа изменить мой запрос Prisma.