Làm thế nào để tối ưu hóa các truy vấn cửa sổ trong postgres


7

Tôi có bảng sau với khoảng 175k hồ sơ:

    Column     |            Type             |              Modifiers
----------------+-----------------------------+-------------------------------------
 id             | uuid                        | not null default uuid_generate_v4()
 competition_id | uuid                        | not null
 user_id        | uuid                        | not null
 first_name     | character varying(255)      | not null
 last_name      | character varying(255)      | not null
 image          | character varying(255)      |
 country        | character varying(255)      |
 slug           | character varying(255)      | not null
 total_votes    | integer                     | not null default 0
 created_at     | timestamp without time zone |
 updated_at     | timestamp without time zone |
 featured_until | timestamp without time zone |
 image_src      | character varying(255)      |
 hidden         | boolean                     | not null default false
 photos_count   | integer                     | not null default 0
 photo_id       | uuid                        |
Indexes:
    "entries_pkey" PRIMARY KEY, btree (id)
    "index_entries_on_competition_id" btree (competition_id)
    "index_entries_on_featured_until" btree (featured_until)
    "index_entries_on_hidden" btree (hidden)
    "index_entries_on_photo_id" btree (photo_id)
    "index_entries_on_slug" btree (slug)
    "index_entries_on_total_votes" btree (total_votes)
    "index_entries_on_user_id" btree (user_id)

và tôi đang thực hiện truy vấn sau để có thứ hạng của mục nhập và sên của mục tiếp theo và mục trước:

WITH entry_with_global_rank AS ( 
  SELECT id
       , rank() OVER w AS global_rank
       , LAG(slug) OVER w AS previous_slug
       , LEAD(slug) OVER w AS next_slug
  FROM entries 
  WHERE competition_id = 'bdd94eee-25a4-481f-b7b5-37aaed953c6b' 
  WINDOW w AS (PARTITION BY competition_id ORDER BY total_votes DESC) 
) 
SELECT * 
FROM entry_with_global_rank 
WHERE id = 'f2df68b7-d720-459d-8c4d-d11e28e0f0c0' 
LIMIT 1;

Đây là kết quả từ EXPLAIN:

                                          QUERY PLAN
-----------------------------------------------------------------------------------------------
 Limit  (cost=516228.88..516233.37 rows=1 width=88)
   CTE entry_with_global_rank
     ->  WindowAgg  (cost=510596.59..516228.88 rows=250324 width=52)
           ->  Sort  (cost=510596.59..511222.40 rows=250324 width=52)
                 Sort Key: entries.total_votes
                 ->  Seq Scan on entries  (cost=0.00..488150.74 rows=250324 width=52)
                       Filter: (competition_id = 'bdd94eee-25a4-481f-b7b5-37aaed953c6b'::uuid)
   ->  CTE Scan on entry_with_global_rank  (cost=0.00..5632.29 rows=1252 width=88)
         Filter: (id = 'f2df68b7-d720-459d-8c4d-d11e28e0f0c0'::uuid)
(9 rows)

Truy vấn này mất ~ 1400ms; Có cách nào để tăng tốc độ này không?

Biên tập:

Đây là kết quả từ EXPLAIN ANALYZE:

                                                               QUERY PLAN
----------------------------------------------------------------------------------------------------------------------------------------
 Limit  (cost=516228.88..516233.37 rows=1 width=88) (actual time=1232.824..1232.824 rows=1 loops=1)
   CTE entry_with_global_rank
     ->  WindowAgg  (cost=510596.59..516228.88 rows=250324 width=52) (actual time=1202.101..1226.846 rows=8727 loops=1)
           ->  Sort  (cost=510596.59..511222.40 rows=250324 width=52) (actual time=1202.069..1213.992 rows=8728 loops=1)
                 Sort Key: entries.total_votes
                 Sort Method: quicksort  Memory: 8128kB
                 ->  Seq Scan on entries  (cost=0.00..488150.74 rows=250324 width=52) (actual time=89.970..1174.083 rows=50335 loops=1)
                       Filter: (competition_id = 'bdd94eee-25a4-481f-b7b5-37aaed953c6b'::uuid)
                       Rows Removed by Filter: 125477
   ->  CTE Scan on entry_with_global_rank  (cost=0.00..5632.29 rows=1252 width=88) (actual time=1232.822..1232.822 rows=1 loops=1)
         Filter: (id = 'f2df68b7-d720-459d-8c4d-d11e28e0f0c0'::uuid)
         Rows Removed by Filter: 8726
 Total runtime: 1234.424 ms
(13 rows)

Chỉnh sửa 2:

Tôi đã chạy VACUUM ANALYZEtrên cơ sở dữ liệu và bây giờ thời gian truy vấn đã được cải thiện, mặc dù tôi chắc chắn phải có một số cách để cải thiện hiệu suất:

                                                                                QUERY PLAN
---------------------------------------------------------------------------------------------------------------------------------------------------------------------------
 Limit  (cost=475372.26..475376.76 rows=1 width=88) (actual time=138.388..138.388 rows=1 loops=1)
   CTE entry_with_global_rank
     ->  WindowAgg  (cost=470662.23..475372.26 rows=209335 width=35) (actual time=125.489..132.214 rows=4178 loops=1)
           ->  Sort  (cost=470662.23..471185.56 rows=209335 width=35) (actual time=125.462..126.724 rows=4179 loops=1)
                 Sort Key: entries.total_votes
                 Sort Method: quicksort  Memory: 5510kB
                 ->  Bitmap Heap Scan on entries  (cost=71390.90..452161.77 rows=209335 width=35) (actual time=29.381..87.130 rows=50390 loops=1)
                       Recheck Cond: (competition_id = 'bdd94eee-25a4-481f-b7b5-37aaed953c6b'::uuid)
                       ->  Bitmap Index Scan on index_entries_on_competition_id  (cost=0.00..71338.56 rows=209335 width=0) (actual time=23.593..23.593 rows=51257 loops=1)
                             Index Cond: (competition_id = 'bdd94eee-25a4-481f-b7b5-37aaed953c6b'::uuid)
   ->  CTE Scan on entry_with_global_rank  (cost=0.00..4710.04 rows=1047 width=88) (actual time=138.387..138.387 rows=1 loops=1)
         Filter: (id = '9470ec4f-fed1-4f95-bbed-1e3dbba5f53b'::uuid)
         Rows Removed by Filter: 4177
 Total runtime: 138.588 ms
(14 rows)

Chỉnh sửa 3:

Theo yêu cầu, kế hoạch truy vấn cuối cùng với chỉ số bao phủ tại chỗ, ngay sau VACUUM ANALYZE:

                                                                              QUERY PLAN
----------------------------------------------------------------------------------------------------------------------------------------------------------------------
 Limit  (cost=0.42..6771.99 rows=1 width=88) (actual time=46.765..46.765 rows=1 loops=1)
   ->  Subquery Scan on entry_with_global_rank  (cost=0.42..6771.99 rows=1 width=88) (actual time=46.763..46.763 rows=1 loops=1)
         Filter: (entry_with_global_rank.id = 'f2df68b7-d720-459d-8c4d-d11e28e0f0c0'::uuid)
         Rows Removed by Filter: 9128
         ->  WindowAgg  (cost=0.42..5635.06 rows=90955 width=35) (actual time=0.090..40.002 rows=9129 loops=1)
               ->  Index Only Scan using entries_extra_special_idx on entries  (cost=0.42..3815.96 rows=90955 width=35) (actual time=0.071..10.973 rows=9130 loops=1)
                     Index Cond: (competition_id = 'bdd94eee-25a4-481f-b7b5-37aaed953c6b'::uuid)
                     Heap Fetches: 166
 Total runtime: 46.867 ms
(9 rows)

Sử dụng trang web này để giải thích giải thích của bạn.depesz.com
Mihai

3
Bạn có tổng cộng 175k hàng nhưng người lập kế hoạch nghĩ rằng bạn có 250k hàng chỉ dành cho competition_id = 'bdd94eee-25a4-481f-b7b5-37aaed953c6b'. Sử dụng GIẢI THÍCH ANALYZE để có được số lượng thực tế. Tính chọn lọc của ID cụ thể này là chìa khóa ở đây.
Daniel Vérité

2
Ngoài ra, điều đó không gây ra sự chậm chạp, nhưng bạn không phải PARTITION BY competition_idchỉ có một giá trị (do WHEREđiều khoản).
Daniel Vérité

Câu trả lời:


7

Các CTE là không cần thiết ở đây và tư thế như rào cản tối ưu hóa. Một truy vấn con đơn giản thường hoạt động tốt hơn:

SELECT * 
FROM  (
   SELECT id
         ,rank()     OVER w AS global_rank
         ,lag(slug)  OVER w AS previous_slug
         ,lead(slug) OVER w AS next_slug 
   FROM   entries 
   WHERE  competition_id = 'bdd94eee-25a4-481f-b7b5-37aaed953c6b' 
   WINDOW w AS (ORDER BY total_votes DESC) 
   ) entry_with_global_rank 
WHERE  id = 'f2df68b7-d720-459d-8c4d-d11e28e0f0c0' 
LIMIT  1;

Như @Daniel đã nhận xét , tôi đã xóa PARTITION BYmệnh đề khỏi định nghĩa cửa sổ, vì competition_iddù sao bạn cũng bị giới hạn trong một lần duy nhất .

Bố trí bảng

Bạn có thể tối ưu hóa bố cục bảng của mình để giảm nhẹ kích thước lưu trữ trên đĩa, điều này giúp mọi thứ nhanh hơn một chút:

     Column     |            Type             |              Modifiers
----------------+-----------------------------+-------------------------------------
 id             | uuid                        | not null default uuid_generate_v4()
 competition_id | uuid                        | not null
 user_id        | uuid                        | not null
 total_votes    | integer                     | not null default 0
 photos_count   | integer                     | not null default 0
 hidden         | boolean                     | not null default false
 slug           | character varying(255)      | not null
 first_name     | character varying(255)      | not null
 last_name      | character varying(255)      | not null
 image          | character varying(255)      |
 country        | character varying(255)      |
 image_src      | character varying(255)      |
 photo_id       | uuid                        |
 created_at     | timestamp without time zone |
 updated_at     | timestamp without time zone |
 featured_until | timestamp without time zone |

Thêm về điều đó:

Ngoài ra, bạn có thực sự cần tất cả các uuidcột? inthoặc bigintsẽ không làm việc cho bạn? Sẽ làm cho bảng và chỉ mục nhỏ hơn một chút và mọi thứ nhanh hơn.

Và tôi sẽ chỉ sử dụng textcho dữ liệu ký tự, nhưng điều đó sẽ không giúp thực hiện truy vấn.

Ngoài ra: character varying(255)hầu như luôn luôn là vô nghĩa trong Postgres. Một số RDBMS khác kiếm lợi từ việc giới hạn độ dài, đối với Postgres, tất cả đều giống nhau (trừ khi bạn thực sự cần phải thực thi độ dài tối đa không mong muốn là 255 ký tự).

Chỉ số đặc biệt

Cuối cùng, bạn có thể xây dựng một chỉ mục chuyên môn cao (chỉ khi bảo trì chỉ số có giá trị vỏ đặc biệt):

CREATE INDEX entries_special_idx ON entries (competition_id, total_votes DESC, id, slug);

Thêm (id, slug)vào chỉ mục chỉ có ý nghĩa nếu bạn có thể quét chỉ mục này. (Tự động vô hiệu hóa hoặc nhiều ghi đồng thời sẽ phủ nhận nỗ lực đó.) Khác loại bỏ hai cột cuối cùng.

Trong khi đang ở đó, kiểm toán các chỉ số của bạn. Có phải tất cả chúng đang được sử dụng? Có thể có một số hàng hóa chết ở đây.


1
Cảm ơn về tổng quan to lớn. Tôi đã cập nhật truy vấn, cắt giảm số lượng chỉ mục và thêm entries_special_idxvà bây giờ truy vấn chạy trong ~ 40ms!
Jim Neath

@JimNeath: Tuyệt! Vì vậy, bạn có được Index Onlyquét trong ANALYZEđầu ra? Bạn có phiền khi thêm một kế hoạch truy vấn vào câu hỏi để so sánh không?
Erwin Brandstetter

Chỉ cần thêm kế hoạch truy vấn :)
Jim Neath

@Jim: Cảm ơn. Làm việc theo cách tôi có trong tâm trí chính xác. :)
Erwin Brandstetter
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.