Tại sao CTE tồi tệ hơn nhiều so với các truy vấn con nội tuyến


11

Tôi đang cố gắng để hiểu rõ hơn về cách trình lập kế hoạch truy vấn hoạt động trong postgresql.

Tôi có truy vấn này:

select id from users 
    where id <> 2
    and gender = (select gender from users where id = 2)
    order by latest_location::geometry <-> (select latest_location from users where id = 2) ASC
    limit 50

Nó chạy trong chưa đầy 10ms trên cơ sở dữ liệu của tôi với khoảng 500 nghìn mục trong bảng người dùng.

Sau đó, tôi nghĩ rằng để tránh các mục con trùng lặp, tôi có thể viết lại truy vấn dưới dạng CTE, như thế này:

with me as (
    select * from users where id = 2
)
select u.id, u.popularity from users u, me 
    where u.gender = me.gender
    order by  u.latest_location::geometry <-> me.latest_location::geometry ASC
    limit 50;

Tuy nhiên, truy vấn viết lại này chạy trong khoảng 1 giây! Lý do tại sao điều này xảy ra? Tôi thấy trong phần giải thích rằng nó không sử dụng chỉ số hình học, nhưng có thể làm gì cho điều đó không? Cảm ơn!

Một cách khác để viết truy vấn là:

select u.id, u.popularity from users u, (select gender, latest_location from users where id = 2) as me 
    where u.gender = me.gender
    order by  u.latest_location::geometry <-> me.latest_location::geometry ASC
    limit 50;

Tuy nhiên, điều này cũng sẽ chậm như CTE.

Mặt khác, nếu tôi trích xuất ra các tham số cho tôi và chèn tĩnh chúng thì truy vấn lại nhanh chóng:

select u.id, u.popularity from users u
    where u.gender = 'male'
    order by  u.latest_location::geometry <-> '0101000000A49DE61DA71C5A403D0AD7A370F54340'::geometry ASC
    limit 50;

Giải thích về truy vấn đầu tiên (nhanh)

 Limit  (cost=5.69..20.11 rows=50 width=36) (actual time=0.512..8.114 rows=50 loops=1)
   InitPlan 1 (returns $0)
     ->  Index Scan using users_pkey on users users_1  (cost=0.42..2.64 rows=1 width=32) (actual time=0.032..0.033 rows=1 loops=1)
           Index Cond: (id = 2)
   InitPlan 2 (returns $1)
     ->  Index Scan using users_pkey on users users_2  (cost=0.42..2.64 rows=1 width=4) (actual time=0.009..0.010 rows=1 loops=1)
           Index Cond: (id = 2)
   ->  Index Scan using users_latest_location_gix on users  (cost=0.41..70796.51 rows=245470 width=36) (actual time=0.509..8.100 rows=50 loops=1)
         Order By: (latest_location <-> $0)
         Filter: (gender = $1)
         Rows Removed by Filter: 20
 Total runtime: 8.211 ms
(12 rows)

Giải thích về truy vấn thứ hai (chậm)

Limit  (cost=62419.82..62419.95 rows=50 width=76) (actual time=1024.963..1024.970 rows=50 loops=1)
   CTE me
     ->  Index Scan using users_pkey on users  (cost=0.42..2.64 rows=1 width=221) (actual time=0.037..0.038 rows=1 loops=1)
           Index Cond: (id = 2)
   ->  Sort  (cost=62417.18..63030.86 rows=245470 width=76) (actual time=1024.959..1024.963 rows=50 loops=1)
         Sort Key: ((u.latest_location <-> me.latest_location))
         Sort Method: top-N heapsort  Memory: 28kB
         ->  Hash Join  (cost=0.03..54262.85 rows=245470 width=76) (actual time=0.122..938.131 rows=288646 loops=1)
               Hash Cond: (u.gender = me.gender)
               ->  Seq Scan on users u  (cost=0.00..49353.41 rows=490941 width=48) (actual time=0.021..465.025 rows=490994 loops=1)
               ->  Hash  (cost=0.02..0.02 rows=1 width=36) (actual time=0.054..0.054 rows=1 loops=1)
                     Buckets: 1024  Batches: 1  Memory Usage: 1kB
                     ->  CTE Scan on me  (cost=0.00..0.02 rows=1 width=36) (actual time=0.047..0.049 rows=1 loops=1)
 Total runtime: 1025.096 ms

3
Tôi đã viết về điều này gần đây; xem blog.2ndquadrant.com/postgresql-ctes-are-optimization-fences . Mặc dù hiện tại có một số vấn đề DNS có thể hạn chế khả năng tiếp cận của trang web đó. Hãy thử một truy vấn con FROMthay vì thuật ngữ CTE để có kết quả tốt nhất.
Craig Ringer

Điều gì nếu bạn sử dụng (select id, latest_location from users where id = 2)như là cte? Có lẽ đó là * nguyên nhân gây ra vấn đề này
cha

Tôi có thể nghĩ rằng bạn sẽ tìm kiếm những người dùng khác giới gần nhất :)
cha

@cha Làm cho không có sự khác biệt về tốc độ để chỉ chọn giới tính và vị trí trong cte. (Trong trường hợp của tôi, tôi muốn lấy trung bình của những người dùng tương tự, chỉ là tôi đã đơn giản hóa truy vấn cho câu hỏi)
Viblo

@CraigRinger Tôi không nghĩ đó là hàng rào tối ưu hóa. Tôi cũng đã thử đề nghị của bạn và nó cũng chậm. Mặt khác, nếu tôi trích xuất các tham số theo cách thủ công thì nó rất nhanh (và đó là một tùy chọn thực trong trường hợp của tôi, kết quả cuối cùng là một hàm dù sao).
Viblo

Câu trả lời:


11

Thử đi:

with me as (
    select * from users where id = 2
)
select u.id, u.popularity from users u, me 
    where u.gender = (select gender from me)
    order by  u.latest_location::geometry <-> (select latest_location from me)::geometry ASC
    limit 50;

Khi tôi nhìn vào kế hoạch nhanh, đây là những gì nhảy ra với tôi (in đậm):

 Giới hạn (chi phí = 5,69..20,11 hàng = 50 chiều rộng = 36) (thời gian thực tế = 0,512..8.114 hàng = 50 vòng lặp = 1)
   InitPlan 1 ( trả về $ 0 )
     -> Quét chỉ mục bằng users_pkey trên người dùng users_1 (chi phí = 0,42..2,64 hàng = 1 width = 32) (thời gian thực tế = 0,032..0.033 hàng = 1 vòng = 1)
           Chỉ số Cond: (id = 2)
   InitPlan 2 ( trả về $ 1 )
     -> Quét chỉ mục bằng users_pkey trên người dùng user_2 (chi phí = 0,42..2,64 hàng = 1 width = 4) (thời gian thực tế = 0,009..0.010 hàng = 1 vòng = 1)
           Chỉ số Cond: (id = 2)
   -> Quét chỉ mục bằng cách sử dụng users_latest_location_gix trên người dùng (chi phí = 0,41., 70796,51 hàng = 245470 chiều rộng = 36) (thời gian thực tế = 0,509..8.100 hàng = 50 vòng = 1)
         Đặt hàng theo: (mới nhất_location   $ 0 )
         Bộ lọc: (giới tính = $ 1 )
         Hàng bị xóa bởi Bộ lọc: 20
 Tổng thời gian chạy: 8.211 ms
(12 hàng)

Trong phiên bản chậm, trình hoạch định truy vấn đang đánh giá toán tử đẳng thức gendervà toán tử hình học trên latest_locationtrong bối cảnh của phép nối , trong đó giá trị từ mecó thể thay đổi theo từng hàng (mặc dù nó chỉ ước tính đúng 1 hàng). Trong phiên bản nhanh, các giá trị của genderlatest_locationđược coi là vô hướng vì chúng được phát ra bởi các truy vấn con nội tuyến, cho biết trình hoạch định truy vấn, nó chỉ có một giá trị của mỗi truy vấn. Đây là cùng một lý do tại sao bạn có được kế hoạch nhanh khi bạn dán các giá trị theo nghĩa đen.


Tôi nghĩ rằng bạn có thể loại bỏ mekhỏi fromđiều khoản bây giờ.
Jarius Hebzo
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.