ปรับแต่งแบบสอบถาม Postgres ด้วย IN ขนาดใหญ่


30

ข้อความค้นหานี้รับรายการโพสต์ที่สร้างโดยคนที่คุณติดตาม คุณสามารถติดตามคนได้ไม่ จำกัด จำนวน แต่คนส่วนใหญ่ติดตามน้อยกว่า 1,000 คน

ด้วยการสืบค้นแบบนี้การเพิ่มประสิทธิภาพที่เห็นได้ชัดคือการแคช"Post"รหัส แต่น่าเสียดายที่ฉันไม่มีเวลาสำหรับตอนนี้

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;

อัตราผลตอบแทน:

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

สิ่งนี้จะได้รับการปรับให้เหมาะสมในขณะนี้ได้อย่างไร

ฉันมีดัชนีที่เกี่ยวข้องดังต่อไปนี้:

-- 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;

บางทีนี่อาจจะต้องมีดัชนีคอมโพสิตขนาดใหญ่บางส่วนที่มีcreatedAtและuserIdที่private IS NULL?

คำตอบ:


29

แทนที่จะใช้INรายการใหญ่เข้าร่วมในVALUESนิพจน์หรือถ้ารายการมีขนาดใหญ่พอให้ใช้ตาราง temp สร้างดัชนีจากนั้นเข้าร่วมกับมัน

มันจะดีถ้า PostgreSQL สามารถทำสิ่งนี้ภายใน & โดยอัตโนมัติ แต่ ณ จุดนี้ผู้วางแผนไม่ทราบวิธี

หัวข้อที่คล้ายกัน:


28

จริงๆแล้วมีสองสายพันธุ์ที่แตกต่างกันของการINสร้างใน Postgres หนึ่งทำงานกับนิพจน์ย่อยแบบสอบถาม (คืนชุด ), อีกอันหนึ่งที่มีรายการของค่าซึ่งเป็นเพียงการย่อ

expression = value1
OR
expression = value2
OR
...

คุณกำลังใช้แบบฟอร์มที่สองซึ่งดีสำหรับรายการสั้น ๆ แต่ช้ากว่ามากสำหรับรายการแบบยาว ระบุรายการค่าของคุณเป็นนิพจน์เคียวรีย่อยแทน ฉันเพิ่งได้ตระหนักถึงตัวแปรนี้ :

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

ฉันชอบที่จะผ่านอาเรย์, ไม่เป็นอันตรายและเข้าร่วม ประสิทธิภาพที่คล้ายกัน แต่ไวยากรณ์สั้นกว่า:

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

เทียบเท่าตราบใดที่ไม่มีรายการซ้ำในชุด / อาร์เรย์ที่ให้ ลองใช้แบบฟอร์มที่สองพร้อมกับJOINส่งคืนแถวที่ซ้ำกันในขณะที่แบบแรกที่มีINจะส่งคืนอินสแตนซ์เดียวเท่านั้น ความแตกต่างเล็กน้อยนี้ทำให้เกิดแผนการสืบค้นที่แตกต่างกันเช่นกัน

"Posts"."userId"เห็นได้ชัดว่าคุณจำเป็นต้องมีดัชนีใน
สำหรับรายการที่ยาวมากๆ (หลายพันรายการ) ไปกับตาราง temp ที่จัดทำดัชนีเช่น @Craig ที่แนะนำ สิ่งนี้ช่วยให้การสแกนดัชนีบิตแมปรวมกันบนทั้งสองตารางซึ่งโดยทั่วไปจะเร็วกว่าทันทีที่มีหลายสิ่งอันดับต่อเพจข้อมูลเพื่อดึงข้อมูลจากดิสก์

ที่เกี่ยวข้อง:

นอกเหนือจาก: หลักการตั้งชื่อของคุณไม่เป็นประโยชน์มากทำให้โค้ดของคุณละเอียดและอ่านยาก แทนที่จะใช้ตัวระบุที่ถูกต้องตัวพิมพ์เล็กและตัวใหญ่

โดยการใช้ไซต์ของเรา หมายความว่าคุณได้อ่านและทำความเข้าใจนโยบายคุกกี้และนโยบายความเป็นส่วนตัวของเราแล้ว
Licensed under cc by-sa 3.0 with attribution required.