Postgres sorgusunu büyük bir IN ile optimize etme


30

Bu sorgu, takip ettiğiniz kişilerin oluşturduğu yayınların bir listesini alır. Sınırsız sayıda kişiyi takip edebilirsiniz, ancak çoğu kişi <1000 kişiyi takip ediyor.

Bu sorgu tarzıyla, bariz optimizasyon "Post"kimlikleri önbelleğe almak olacaktır , fakat ne yazık ki şu an bunun için zamanım yok.

EXPLAIN ANALYZE SELECT
    "Post"."id",
    "Post"."actionId",
    "Post"."commentCount",
    ...
FROM
    "Posts" AS "Post"
INNER JOIN "Users" AS "user" ON "Post"."userId" = "user"."id"
LEFT OUTER JOIN "ActivityLogs" AS "activityLog" ON "Post"."activityLogId" = "activityLog"."id"
LEFT OUTER JOIN "WeightLogs" AS "weightLog" ON "Post"."weightLogId" = "weightLog"."id"
LEFT OUTER JOIN "Workouts" AS "workout" ON "Post"."workoutId" = "workout"."id"
LEFT OUTER JOIN "WorkoutLogs" AS "workoutLog" ON "Post"."workoutLogId" = "workoutLog"."id"
LEFT OUTER JOIN "Workouts" AS "workoutLog.workout" ON "workoutLog"."workoutId" = "workoutLog.workout"."id"
WHERE
"Post"."userId" IN (
    201486,
    1825186,
    998608,
    340844,
    271909,
    308218,
    341986,
    216893,
    1917226,
    ...  -- many more
)
AND "Post"."private" IS NULL
ORDER BY
    "Post"."createdAt" DESC
LIMIT 10;

Verim:

Limit  (cost=3.01..4555.20 rows=10 width=2601) (actual time=7923.011..7973.138 rows=10 loops=1)
  ->  Nested Loop Left Join  (cost=3.01..9019264.02 rows=19813 width=2601) (actual time=7923.010..7973.133 rows=10 loops=1)
        ->  Nested Loop Left Join  (cost=2.58..8935617.96 rows=19813 width=2376) (actual time=7922.995..7973.063 rows=10 loops=1)
              ->  Nested Loop Left Join  (cost=2.15..8821537.89 rows=19813 width=2315) (actual time=7922.984..7961.868 rows=10 loops=1)
                    ->  Nested Loop Left Join  (cost=1.71..8700662.11 rows=19813 width=2090) (actual time=7922.981..7961.846 rows=10 loops=1)
                          ->  Nested Loop Left Join  (cost=1.29..8610743.68 rows=19813 width=2021) (actual time=7922.977..7961.816 rows=10 loops=1)
                                ->  Nested Loop  (cost=0.86..8498351.81 rows=19813 width=1964) (actual time=7922.972..7960.723 rows=10 loops=1)
                                      ->  Index Scan using posts_createdat_public_index on "Posts" "Post"  (cost=0.43..8366309.39 rows=20327 width=261) (actual time=7922.869..7960.509 rows=10 loops=1)
                                            Filter: ("userId" = ANY ('{201486,1825186,998608,340844,271909,308218,341986,216893,1917226, ... many more ...}'::integer[]))
                                            Rows Removed by Filter: 218360
                                      ->  Index Scan using "Users_pkey" on "Users" "user"  (cost=0.43..6.49 rows=1 width=1703) (actual time=0.005..0.006 rows=1 loops=10)
                                            Index Cond: (id = "Post"."userId")
                                ->  Index Scan using "ActivityLogs_pkey" on "ActivityLogs" "activityLog"  (cost=0.43..5.66 rows=1 width=57) (actual time=0.107..0.107 rows=0 loops=10)
                                      Index Cond: ("Post"."activityLogId" = id)
                          ->  Index Scan using "WeightLogs_pkey" on "WeightLogs" "weightLog"  (cost=0.42..4.53 rows=1 width=69) (actual time=0.001..0.001 rows=0 loops=10)
                                Index Cond: ("Post"."weightLogId" = id)
                    ->  Index Scan using "Workouts_pkey" on "Workouts" workout  (cost=0.43..6.09 rows=1 width=225) (actual time=0.001..0.001 rows=0 loops=10)
                          Index Cond: ("Post"."workoutId" = id)
              ->  Index Scan using "WorkoutLogs_pkey" on "WorkoutLogs" "workoutLog"  (cost=0.43..5.75 rows=1 width=61) (actual time=1.118..1.118 rows=0 loops=10)
                    Index Cond: ("Post"."workoutLogId" = id)
        ->  Index Scan using "Workouts_pkey" on "Workouts" "workoutLog.workout"  (cost=0.43..4.21 rows=1 width=225) (actual time=0.004..0.004 rows=0 loops=10)
              Index Cond: ("workoutLog"."workoutId" = id)
Total runtime: 7974.524 ms

Bu şu an için nasıl optimize edilebilir?

Aşağıdaki ilgili endekslere sahibim:

-- Gets used
CREATE INDEX  "posts_createdat_public_index" ON "public"."Posts" USING btree("createdAt" DESC) WHERE "private" IS null;
-- Don't get used
CREATE INDEX  "posts_userid_fk_index" ON "public"."Posts" USING btree("userId");
CREATE INDEX  "posts_following_index" ON "public"."Posts" USING btree("userId", "createdAt" DESC) WHERE "private" IS null;

Belki de bu createdAtve userIdnerede ve daha büyük bir kısmi bileşik indeks gerektirir private IS NULL?

Yanıtlar:



28

INPostgres'te aslında yapının iki farklı çeşidi var . Biri bir alt sorgu ifadesiyle (bir kümeyi döndürür ), diğeri ise sadece kısayol olan bir değerler listesiyle çalışır

expression = value1
OR
expression = value2
OR
...

İkinci bir form kullanıyorsunuz, bu kısa bir liste için iyi, ancak uzun listeler için çok daha yavaş. Değer listenizi bunun yerine alt sorgu ifadesi olarak sağlayın. Geçenlerde bu varyanttan haberdar oldum :

WHERE "Post"."userId" IN (VALUES (201486), (1825186), (998608), ... )

Bir diziyi geçmeyi, dürüst olmayı ve ona katılmayı seviyorum. Benzer performans, ancak sözdizimi daha kısa:

...
FROM   unnest('{201486,1825186,998608, ...}'::int[]) "userId"
JOIN   "Posts" "Post" USING ("userId")

Verilen sette / dizide çift olmadıkça eşdeğerdir . Başka bir JOINdöner bir ikinci kopya döndürür , ikincisi INsadece bir tek örnek döndürür. Bu ince fark , farklı sorgu planlarına da neden olur.

Açıkçası, bir endekse ihtiyacınız var "Posts"."userId".
İçin çok @Craig önerdi gibi uzun listeleri (binlerce), dizinlenmiş bir geçici tablo ile gitmek. Bu, birleştirilmiş bitmap indeksinin her iki tablonun da taranmasına izin verir; bu, genellikle her veri sayfası için diskten alınacak birden fazla tuple olduğu anda daha hızlıdır.

İlgili:

Bir yana: adlandırma kuralınız çok yardımcı olmuyor, kodunuzu ayrıntılı ve okunması zorlaştırıyor. Aksine, yasal, küçük harf, işaretsiz tanımlayıcıları kullanın.

Sitemizi kullandığınızda şunları okuyup anladığınızı kabul etmiş olursunuz: Çerez Politikası ve Gizlilik Politikası.
Licensed under cc by-sa 3.0 with attribution required.