Tại sao PostgreSQL chọn thứ tự tham gia đắt tiền hơn?


13

PostgreSQL sử dụng mặc định, cộng với

default_statistics_target=1000
random_page_cost=1.5

Phiên bản

PostgreSQL 10.4 on x86_64-pc-linux-musl, compiled by gcc (Alpine 6.4.0) 6.4.0, 64-bit

Tôi đã hút bụi và phân tích. Truy vấn rất đơn giản:

SELECT r.price
FROM account_payer ap
  JOIN account_contract ac ON ap.id = ac.account_payer_id
  JOIN account_schedule "as" ON ac.id = "as".account_contract_id
  JOIN schedule s ON "as".id = s.account_schedule_id
  JOIN rate r ON s.id = r.schedule_id
WHERE ap.account_id = 8

Mỗi idcột là khóa chính và mọi thứ được tham gia đều là mối quan hệ khóa ngoài và mỗi khóa ngoại có một chỉ mục. Cộng với một chỉ số cho account_payer.account_id.

Phải mất 3,93 giây để trả lại 76k hàng.

Merge Join  (cost=8.06..83114.08 rows=3458267 width=6) (actual time=0.228..3920.472 rows=75548 loops=1)
  Merge Cond: (s.account_schedule_id = "as".id)
  ->  Nested Loop  (cost=0.57..280520.54 rows=6602146 width=14) (actual time=0.163..3756.082 rows=448173 loops=1)
        ->  Index Scan using schedule_account_schedule_id_idx on schedule s  (cost=0.14..10.67 rows=441 width=16) (actual time=0.035..0.211 rows=89 loops=1)
        ->  Index Scan using rate_schedule_id_code_modifier_facility_idx on rate r  (cost=0.43..486.03 rows=15005 width=10) (actual time=0.025..39.903 rows=5036 loops=89)
              Index Cond: (schedule_id = s.id)
  ->  Materialize  (cost=0.43..49.46 rows=55 width=8) (actual time=0.060..12.984 rows=74697 loops=1)
        ->  Nested Loop  (cost=0.43..49.32 rows=55 width=8) (actual time=0.048..1.110 rows=66 loops=1)
              ->  Nested Loop  (cost=0.29..27.46 rows=105 width=16) (actual time=0.030..0.616 rows=105 loops=1)
                    ->  Index Scan using account_schedule_pkey on account_schedule "as"  (cost=0.14..6.22 rows=105 width=16) (actual time=0.014..0.098 rows=105 loops=1)
                    ->  Index Scan using account_contract_pkey on account_contract ac  (cost=0.14..0.20 rows=1 width=16) (actual time=0.003..0.003 rows=1 loops=105)
                          Index Cond: (id = "as".account_contract_id)
              ->  Index Scan using account_payer_pkey on account_payer ap  (cost=0.14..0.21 rows=1 width=8) (actual time=0.003..0.003 rows=1 loops=105)
                    Index Cond: (id = ac.account_payer_id)
                    Filter: (account_id = 8)
                    Rows Removed by Filter: 0
Planning time: 5.843 ms
Execution time: 3929.317 ms

Nếu tôi đặt join_collapse_limit=1, phải mất 0,16 giây, tăng tốc 25 lần.

Nested Loop  (cost=6.32..147323.97 rows=3458267 width=6) (actual time=8.908..151.860 rows=75548 loops=1)
  ->  Nested Loop  (cost=5.89..390.23 rows=231 width=8) (actual time=8.730..11.655 rows=66 loops=1)
        Join Filter: ("as".id = s.account_schedule_id)
        Rows Removed by Join Filter: 29040
        ->  Index Scan using schedule_pkey on schedule s  (cost=0.27..17.65 rows=441 width=16) (actual time=0.014..0.314 rows=441 loops=1)
        ->  Materialize  (cost=5.62..8.88 rows=55 width=8) (actual time=0.001..0.011 rows=66 loops=441)
              ->  Hash Join  (cost=5.62..8.61 rows=55 width=8) (actual time=0.240..0.309 rows=66 loops=1)
                    Hash Cond: ("as".account_contract_id = ac.id)
                    ->  Seq Scan on account_schedule "as"  (cost=0.00..2.05 rows=105 width=16) (actual time=0.010..0.028 rows=105 loops=1)
                    ->  Hash  (cost=5.02..5.02 rows=48 width=8) (actual time=0.178..0.178 rows=61 loops=1)
                          Buckets: 1024  Batches: 1  Memory Usage: 11kB
                          ->  Hash Join  (cost=1.98..5.02 rows=48 width=8) (actual time=0.082..0.143 rows=61 loops=1)
                                Hash Cond: (ac.account_payer_id = ap.id)
                                ->  Seq Scan on account_contract ac  (cost=0.00..1.91 rows=91 width=16) (actual time=0.007..0.023 rows=91 loops=1)
                                ->  Hash  (cost=1.64..1.64 rows=27 width=8) (actual time=0.048..0.048 rows=27 loops=1)
                                      Buckets: 1024  Batches: 1  Memory Usage: 10kB
                                      ->  Seq Scan on account_payer ap  (cost=0.00..1.64 rows=27 width=8) (actual time=0.009..0.023 rows=27 loops=1)
                                            Filter: (account_id = 8)
                                            Rows Removed by Filter: 24
  ->  Index Scan using rate_schedule_id_code_modifier_facility_idx on rate r  (cost=0.43..486.03 rows=15005 width=10) (actual time=0.018..1.685 rows=1145 loops=66)
        Index Cond: (schedule_id = s.id)
Planning time: 4.692 ms
Execution time: 160.585 ms

Những đầu ra này làm cho rất ít ý nghĩa với tôi. Đầu tiên có chi phí (rất cao) là 280.500 cho phép nối vòng lặp lồng nhau cho các chỉ số lịch trình và tỷ lệ. Tại sao PostgreSQL cố tình chọn tham gia rất tốn kém đó trước tiên?

Thông tin bổ sung được yêu cầu thông qua ý kiến

rate_schedule_id_code_modifier_facility_idxmột chỉ số ghép?

Đó là, với schedule_idviệc là cột đầu tiên. Tôi đã biến nó thành một chỉ mục chuyên dụng và nó được chọn bởi trình hoạch định truy vấn, nhưng nó không ảnh hưởng đến hiệu suất hoặc ảnh hưởng đến kế hoạch.


Bạn có thể thay đổi cài đặt default_statistics_targetrandom_page_costtrở lại mặc định của họ không? Điều gì xảy ra khi bạn default_statistics_targetvẫn tăng hơn nữa? Bạn có thể tạo DB Fiddle (tại dbfiddle.uk) và cố gắng tái tạo vấn đề ở đó không?
Colin 't Hart

3
Bạn có thể kiểm tra số liệu thống kê thực tế để xem liệu có gì đó sai lệch / kỳ lạ về dữ liệu của bạn không? postgresql.org/docs/10/static/planner-stats.html
Colin 't Hart

Giá trị hiện tại cho tham số là work_memgì? Thay đổi nó cho thời gian khác nhau?
eppesuig

Câu trả lời:


1

Có vẻ như số liệu thống kê của bạn không chính xác (chạy phân tích chân không để làm mới chúng) hoặc bạn có các cột tương quan trong mô hình của mình (và do đó bạn sẽ cần phải thực hiện create statisticsđể thông báo cho người lập kế hoạch về thực tế đó).

Các join_collapsetham số cho phép các nhà quy hoạch sắp xếp lại gia nhập nên nó thực hiện đầu tiên một trong đó lấy về dữ liệu ít hơn. Nhưng, để thực hiện, chúng ta không thể để trình hoạch định thực hiện điều đó trên một truy vấn có nhiều phép nối. Theo mặc định, nó được đặt thành 8 tham gia tối đa. Bằng cách đặt nó thành 1, bạn chỉ cần vô hiệu hóa khả năng đó.

Vậy làm thế nào để postgres thấy trước được bao nhiêu hàng truy vấn này nên tìm nạp? Nó sử dụng số liệu thống kê để ước tính số lượng hàng.

Những gì chúng ta có thể thấy trong các kế hoạch giải thích của bạn là có một số ước tính số hàng không chính xác (giá trị đầu tiên là ước tính, thứ hai là thực tế).

Ví dụ, ở đây:

Materialize  (cost=0.43..49.46 rows=55 width=8) (actual time=0.060..12.984 rows=74697 loops=1)

Người lập kế hoạch ước tính có được 55 hàng khi anh ta thực sự có 74697.

Những gì tôi sẽ làm (nếu tôi ở trong đôi giày của bạn) là:

  • analyze năm bảng liên quan để làm mới số liệu thống kê
  • phát lại explain analyze
  • Nhìn vào sự khác biệt giữa số hàng ước tính và số hàng thực tế
  • Nếu số hàng ước tính là đúng, có thể kế hoạch đã thay đổi và hiệu quả hơn. Nếu mọi thứ đều ổn, bạn có thể cân nhắc thay đổi cài đặt tự động để phân tích (và chân không) thực hiện thường xuyên hơn
  • Nếu số hàng ước tính vẫn sai, có vẻ như bạn có dữ liệu tương quan trong bảng của mình (vi phạm biểu mẫu thông thường thứ ba). Bạn có thể xem xét khai báo với CREATE STATISTICS(tài liệu ở đây )

Nếu bạn cần thêm thông tin về ước tính hàng và tính toán của hàng, bạn sẽ tìm thấy mọi thứ bạn cần trong bài nói chuyện của Tomas Vondra "Tạo số liệu thống kê - Nó dùng để làm gì?" (slide ở đây )

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.