Chỉ mục trên khóa chính không được sử dụng trong tham gia đơn giản


16

Tôi có các định nghĩa bảng và chỉ mục sau đây:

CREATE TABLE munkalap (
    munkalap_id serial PRIMARY KEY,
    ...
);

CREATE TABLE munkalap_lepes (
    munkalap_lepes_id serial PRIMARY KEY,
    munkalap_id integer REFERENCES munkalap (munkalap_id),
    ...
);

CREATE INDEX idx_munkalap_lepes_munkalap_id ON munkalap_lepes (munkalap_id);

Tại sao không có chỉ mục nào trên munkalap_id được sử dụng trong truy vấn sau đây?

EXPLAIN ANALYZE SELECT ml.* FROM munkalap m JOIN munkalap_lepes ml USING (munkalap_id);

QUERY PLAN
Hash Join  (cost=119.17..2050.88 rows=38046 width=214) (actual time=0.824..18.011 rows=38046 loops=1)
  Hash Cond: (ml.munkalap_id = m.munkalap_id)
  ->  Seq Scan on munkalap_lepes ml  (cost=0.00..1313.46 rows=38046 width=214) (actual time=0.005..4.574 rows=38046 loops=1)
  ->  Hash  (cost=78.52..78.52 rows=3252 width=4) (actual time=0.810..0.810 rows=3253 loops=1)
        Buckets: 1024  Batches: 1  Memory Usage: 115kB
        ->  Seq Scan on munkalap m  (cost=0.00..78.52 rows=3252 width=4) (actual time=0.003..0.398 rows=3253 loops=1)
Total runtime: 19.786 ms

Nó giống nhau ngay cả khi tôi thêm bộ lọc:

EXPLAIN ANALYZE SELECT ml.* FROM munkalap m JOIN munkalap_lepes ml USING (munkalap_id) WHERE NOT lezarva;

QUERY PLAN
Hash Join  (cost=79.60..1545.79 rows=1006 width=214) (actual time=0.616..10.824 rows=964 loops=1)
  Hash Cond: (ml.munkalap_id = m.munkalap_id)
  ->  Seq Scan on munkalap_lepes ml  (cost=0.00..1313.46 rows=38046 width=214) (actual time=0.007..5.061 rows=38046 loops=1)
  ->  Hash  (cost=78.52..78.52 rows=86 width=4) (actual time=0.587..0.587 rows=87 loops=1)
        Buckets: 1024  Batches: 1  Memory Usage: 4kB
        ->  Seq Scan on munkalap m  (cost=0.00..78.52 rows=86 width=4) (actual time=0.014..0.560 rows=87 loops=1)
              Filter: (NOT lezarva)
Total runtime: 10.911 ms

Câu trả lời:


21

Nhiều người đã nghe hướng dẫn rằng "quét tuần tự là xấu" và tìm cách loại bỏ chúng khỏi kế hoạch của họ, nhưng nó không đơn giản như vậy. Nếu một truy vấn sẽ bao gồm mọi hàng trong một bảng, quét tuần tự là cách nhanh nhất để có được các hàng đó. Đây là lý do tại sao truy vấn tham gia ban đầu của bạn sử dụng quét seq, bởi vì tất cả các hàng trong cả hai bảng đều được yêu cầu.

Khi lập kế hoạch truy vấn, công cụ lập kế hoạch của Postgres ước tính chi phí của các hoạt động khác nhau (tính toán, tuần tự và IO ngẫu nhiên) theo các sơ đồ có thể khác nhau và chọn kế hoạch mà nó ước tính có chi phí thấp nhất. Khi thực hiện IO từ lưu trữ quay (đĩa), IO ngẫu nhiên thường chậm hơn đáng kể so với IO tuần tự, cấu hình pg mặc định cho Random_page_cost và seq_page_cost ước tính chênh lệch 4: 1 về chi phí.

Những cân nhắc này có hiệu lực khi xem xét phương thức nối hoặc bộ lọc sử dụng chỉ mục so với phương pháp quét tuần tự một bảng. Khi sử dụng một chỉ mục, kế hoạch có thể tìm thấy một hàng một cách nhanh chóng thông qua chỉ mục, sau đó phải tính đến một khối ngẫu nhiên được đọc để giải quyết dữ liệu hàng. Trong trường hợp truy vấn thứ hai của bạn có thêm một biến vị ngữ lọc WHERE NOT lezarva, bạn có thể thấy cách điều này thực hiện các ước tính lập kế hoạch trong kết quả GIẢI QUYẾT PHÂN TÍCH. Công cụ lập kế hoạch ước tính 1006 hàng kết quả từ phép nối (khá khớp với tập kết quả thực tế của 964). Cho rằng munkalap_lepes của bảng lớn hơn chứa khoảng 38K hàng, trình hoạch định thấy rằng phép nối sẽ phải truy cập khoảng 1006/38046 hoặc 1/38 của các hàng trong bảng. Nó cũng biết chiều rộng hàng avg là 214 byte và một khối là 8K, vì vậy có khoảng 38 hàng / khối.

Với các thống kê này, người lập kế hoạch cho rằng có khả năng tham gia sẽ phải đọc tất cả hoặc hầu hết các khối dữ liệu của bảng. Do việc tra cứu chỉ mục cũng không miễn phí và tính toán để quét một khối đánh giá điều kiện bộ lọc rất rẻ so với IO, nên trình hoạch định đã chọn quét tuần tự bảng và tránh chi phí chỉ mục và đọc ngẫu nhiên khi tính toán quét seq sẽ nhanh hơn

Trong thế giới thực, dữ liệu thường có sẵn trong bộ nhớ thông qua bộ đệm trang của hệ điều hành và do đó, không phải mọi khối đọc đều yêu cầu IO. Có thể khá khó để dự đoán mức độ hiệu quả của bộ đệm đối với một truy vấn nhất định, nhưng trình lập kế hoạch PG sử dụng một số phương pháp phỏng đoán đơn giản. Giá trị cấu hình có hiệu lực_cache_size thông báo cho các nhà lập kế hoạch ước tính về khả năng phát sinh chi phí IO thực tế. Giá trị lớn hơn sẽ khiến nó ước tính chi phí thấp hơn cho IO ngẫu nhiên và do đó có thể thiên vị nó theo phương pháp điều khiển chỉ mục qua quét liên tiếp.


Cảm ơn, đây là mô tả hay nhất (và ngắn gọn nhất) mà tôi đã đọc. Làm rõ một vài điểm chính.
dezso

1
Giải thích tuyệt vời. Việc tính toán các hàng / trang dữ liệu là một chút, mặc dù. Bạn phải tính đến tiêu đề trang (24 byte) + 4 byte cho mỗi con trỏ mục trên mỗi hàng + tiêu đề hàng HeapTupleHeader(23 byte mỗi hàng) + bitcoin NULL + căn chỉnh theo MAXALIGN. Cuối cùng, một lượng đệm không xác định do căn chỉnh dữ liệu tùy thuộc vào kiểu dữ liệu của các cột và trình tự của chúng. Tất cả trong tất cả không có quá 33 hàng trên một trang 8 kb trong trường hợp này. (Không tính đến TOAST vào tài khoản.)
Erwin Brandstetter

1
@ErwinBrandstetter Cảm ơn bạn đã điền các phép tính kích thước hàng chính xác hơn. Tôi đã luôn giả định đầu ra ước tính độ rộng hàng bằng cách giải thích sẽ bao gồm các cân nhắc trên mỗi hàng như tiêu đề và NULL-bitmask, nhưng không phải chi phí cấp trang.
dbenhur

1
@dbenhur: Bạn có thể chạy nhanh EXPLAIN ANALYZE SELECT foo from barvới bảng giả cơ bản để xác minh. Ngoài ra, không gian trên đĩa thực tế phụ thuộc vào căn chỉnh dữ liệu, sẽ khó có thể tính đến khi chỉ lấy một số hàng. Độ rộng hàng trong EXPLAINbiểu thị yêu cầu không gian cơ bản cho tập hợp các cột được truy xuất.
Erwin Brandstetter

5

Bạn đang truy xuất tất cả các hàng từ cả hai bảng, do đó không có lợi ích thực sự bằng cách sử dụng quét chỉ mục. Quét chỉ mục chỉ có ý nghĩa nếu bạn chỉ chọn một vài hàng từ một bảng (thường dưới 10% -15%)


Đúng, bạn đã đúng :) Tôi đã cố gắng làm rõ tình huống với một trường hợp cụ thể hơn, xem truy vấn cuối cùng.
dezso

@dezso: Điều tương tự. Nếu bạn có một chỉ mục trên (lezarva, munkalap_id)và nó đủ chọn lọc, thì nó có thể được sử dụng. Điều NOTđó làm cho ít khả năng hơn.
ypercubeᵀᴹ

Tôi đã thêm một chỉ mục một phần dựa trên đề xuất của bạn và nó được sử dụng, vì vậy một nửa vấn đề được giải quyết. Nhưng tôi không hy vọng chỉ số trên khóa ngoại sẽ vô dụng khi tôi muốn THAM GIA chỉ với 87 giá trị so với 3252 ban đầu
dezso

1
@dezso Các hàng có chiều rộng 214 byte, do đó bạn sẽ có ít hơn 40 hàng trên mỗi khối dữ liệu 8K. Độ chọn lọc của chỉ số cũng vào khoảng 1/40 (1006/38046). Vì vậy, PG cho rằng việc đọc tất cả các khối theo tuần tự rẻ hơn thì việc đọc có thể xảy ra về cùng một số khối ngẫu nhiên khi sử dụng chỉ mục. Các giao dịch được ước tính này có thể bị ảnh hưởng bởi các giá trị cấu hình hiệu quả_cache_size và Random_page_cost.
dbenhur

@dbenhur: bạn có thể đưa ra nhận xét của mình một câu trả lời thích hợp không?
dezso
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.