Tối ưu hóa truy vấn Postgres với IN lớn


30

Truy vấn này nhận được một danh sách các bài đăng được tạo bởi những người bạn theo dõi. Bạn có thể theo dõi số lượng người không giới hạn, nhưng hầu hết mọi người theo dõi <1000 người khác.

Với kiểu truy vấn này, tối ưu hóa rõ ràng sẽ là lưu trữ các "Post"id, nhưng tiếc là tôi không có thời gian cho việc đó ngay bây giờ.

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;

Sản lượng:

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

Làm thế nào điều này có thể được tối ưu hóa cho thời điểm hiện tại?

Tôi có các chỉ số liên quan sau đây:

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

Có lẽ điều này đòi hỏi một chỉ số tổng hợp một phần lớn với createdAtuserIdở đâu private IS NULL?

Câu trả lời:


29

Thay vì sử dụng một danh sách lớn IN, hãy tham gia vào một VALUESbiểu thức hoặc nếu danh sách đủ lớn, hãy sử dụng bảng tạm thời, lập chỉ mục cho nó, sau đó tham gia vào biểu thức đó.

Sẽ thật tuyệt nếu PostgreSQL có thể thực hiện việc này trong nội bộ & tự động nhưng tại thời điểm này, người lập kế hoạch không biết làm thế nào.

Chủ đề tương tự:


28

Thực tế, có hai biến thể khác nhau của INcấu trúc trong Postgres. Một hoạt động với biểu thức truy vấn con (trả về một tập hợp ), một biểu thức khác có danh sách các giá trị , chỉ là viết tắt cho

expression = value1
OR
expression = value2
OR
...

Bạn đang sử dụng mẫu thứ hai, tốt cho danh sách ngắn, nhưng chậm hơn nhiều cho danh sách dài. Cung cấp danh sách các giá trị của bạn dưới dạng biểu thức truy vấn con thay thế. Gần đây tôi đã biết về biến thể này :

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

Tôi thích vượt qua một mảng, không cần thiết và tham gia vào nó. Hiệu suất tương tự, nhưng cú pháp ngắn hơn:

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

Tương đương miễn là không có bản sao trong tập / mảng được cung cấp. Khác dạng thứ hai với một JOINhàng trùng lặp trả về, trong khi dạng thứ nhất cóIN nhất chỉ trả về một thể hiện duy nhất. Sự khác biệt tinh tế này cũng gây ra các kế hoạch truy vấn khác nhau.

Rõ ràng, bạn cần một chỉ số trên "Posts"."userId".
Đối với các danh sách rất dài (hàng nghìn), hãy sử dụng bảng tạm thời được lập chỉ mục như @Craig đề xuất. Điều này cho phép quét chỉ mục bitmap kết hợp trên cả hai bảng, thường nhanh hơn ngay khi có nhiều bộ dữ liệu trên mỗi trang dữ liệu để tìm nạp từ đĩa.

Liên quan:

Ngoài ra: quy ước đặt tên của bạn không hữu ích lắm, làm cho mã của bạn dài dòng và khó đọc. Thay vì sử dụng định danh hợp pháp, chữ thường, không trích dẫn.

Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.