Quét chỉ mục (Chỉ) -> Quét chỉ mục bitmap -> Quét tuần tự
Đối với một số hàng, nó trả tiền để chạy quét chỉ mục. Nếu tất cả các trang dữ liệu đều có thể nhìn thấy đủ các trang dữ liệu (=đủ chân không và tải ghi đồng thời không quá nhiều) và chỉ mục có thể cung cấp tất cả các giá trị cột cần thiết, thì chỉ mục quét nhanh hơn được sử dụng. Với nhiều hàng hơn dự kiến sẽ được trả lại (tỷ lệ phần trăm của bảng cao hơn và tùy thuộc vào phân phối dữ liệu, tần số giá trị và độ rộng của hàng), nhiều khả năng sẽ tìm thấy nhiều hàng trên một trang dữ liệu hơn. Sau đó, nó trả tiền để chuyển sang quét chỉ mục bitmap. (Hoặc kết hợp nhiều chỉ mục riêng biệt.) Dù sao đi nữa thì một phần trăm lớn các trang dữ liệu vẫn phải được truy cập, sẽ rẻ hơn nếu bạn chạy quét tuần tự, lọc các hàng thặng dư và bỏ qua hoàn toàn chi phí cho các chỉ mục.
Việc sử dụng chỉ mục trở nên rẻ hơn (nhiều) và có nhiều khả năng hơn khi truy cập các trang dữ liệu theo thứ tự ngẫu nhiên không đắt hơn (nhiều) so với việc truy cập chúng theo thứ tự tuần tự. Đó là trường hợp khi sử dụng SSD thay vì đĩa quay, hoặc thậm chí nhiều hơn để càng nhiều càng được lưu trong bộ nhớ cache - và các thông số cấu hình tương ứng random_page_cost
và effective_cache_size
được đặt cho phù hợp.
Trong trường hợp của bạn, Postgres chuyển sang quét tuần tự, hy vọng tìm thấy rows=263962
, đó đã là 3% của toàn bộ bảng. (Trong khi chỉ rows=47935
thực sự được tìm thấy, hãy xem bên dưới.)
Thêm trong câu trả lời có liên quan này:
- Truy vấn PostgreSQL hiệu quả trên dấu thời gian bằng cách sử dụng chỉ mục hoặc quét chỉ mục bitmap?
Cẩn thận với việc buộc các kế hoạch truy vấn
Bạn không thể bắt buộc một phương pháp lập kế hoạch nhất định trực tiếp trong Postgres, nhưng bạn có thể thực hiện khác các phương pháp dường như cực kỳ tốn kém cho mục đích gỡ lỗi. Xem Cấu hình Phương pháp Lập kế hoạch trong sách hướng dẫn.
SET enable_seqscan = off
(như được gợi ý trong một câu trả lời khác) thực hiện điều đó với các lần quét tuần tự. Nhưng điều đó chỉ dành cho mục đích gỡ lỗi trong phiên của bạn. Đừng không sử dụng cài đặt này làm cài đặt chung trong quá trình sản xuất trừ khi bạn biết chính xác mình đang làm gì. Nó có thể buộc các kế hoạch truy vấn vô lý. Hướng dẫn sử dụng:
Các tham số cấu hình này cung cấp một phương pháp thô sơ để ảnh hưởng đến các kế hoạch truy vấn được chọn bởi trình tối ưu hóa truy vấn. Nếu kế hoạch mặc định được trình tối ưu hóa chọn cho một truy vấn cụ thể không phải là tối ưu, một tạm thời giải pháp là sử dụng một trong các thông số cấu hình này để buộc trình tối ưu hóa chọn một gói khác. Các cách tốt hơn để cải thiện chất lượng của các kế hoạch do trình tối ưu hóa chọn bao gồm điều chỉnh các hằng số chi phí của trình lập kế hoạch (xem Phần 19.7.2), chạy
ANALYZE
theo cách thủ công, tăng giá trị củadefault_statistics_target
tham số cấu hình và tăng số lượng thống kê được thu thập cho các cột cụ thể bằng cách sử dụngALTER TABLE SET STATISTICS
.
Đó đã là hầu hết những lời khuyên bạn cần.
- Giữ cho PostgreSQL đôi khi chọn một kế hoạch truy vấn không tốt
Trong trường hợp cụ thể này, Postgres dự kiến số lần truy cập vào email_activities.email_recipient_id
nhiều gấp 5-6 lần so với thực tế được tìm thấy:
ước tính
rows=227007
so vớiactual ... rows=40789
ước tínhrows=263962
so vớiactual ... rows=47935
Nếu bạn chạy truy vấn này thường xuyên, bạn sẽ phải trả tiền để có ANALYZE
nhìn vào một mẫu lớn hơn để có số liệu thống kê chính xác hơn về cột cụ thể. Bảng của bạn lớn (~ 10 triệu hàng), vì vậy hãy làm như vậy:
ALTER TABLE email_activities ALTER COLUMN email_recipient_id
SET STATISTICS 3000; -- max 10000, default 100
Sau đó, ANALYZE email_activities;
Biện pháp cuối cùng
Trong rất hiếm các trường hợp bạn có thể sử dụng để buộc chỉ mục với SET LOCAL enable_seqscan = off
trong một giao dịch riêng biệt hoặc trong một chức năng với môi trường riêng của nó. Như:
CREATE OR REPLACE FUNCTION f_count_dist_recipients(_email_campaign_id int, _limit int)
RETURNS bigint AS
$func$
SELECT COUNT(DISTINCT a.email_recipient_id)
FROM email_activities a
WHERE a.email_recipient_id IN (
SELECT id
FROM email_recipients
WHERE email_campaign_id = $1
LIMIT $2) -- or consider query below
$func$ LANGUAGE sql VOLATILE COST 100000 SET enable_seqscan = off;
Cài đặt chỉ áp dụng cho phạm vi cục bộ của hàm.
Cảnh báo: Đây chỉ là một bằng chứng về khái niệm. Ngay cả sự can thiệp thủ công ít triệt để hơn nhiều này cũng có thể gây hại cho bạn về lâu dài. Cardinalities, tần số giá trị, lược đồ của bạn, cài đặt Postgres toàn cầu, mọi thứ thay đổi theo thời gian. Bạn sẽ nâng cấp lên phiên bản Postgres mới. Kế hoạch truy vấn mà bạn bắt buộc bây giờ, có thể trở thành một ý tưởng rất tồi sau này.
Và thông thường, đây chỉ là một giải pháp thay thế cho sự cố với thiết lập của bạn. Tốt hơn hãy tìm và sửa nó.
Truy vấn thay thế
Thông tin cần thiết bị thiếu trong câu hỏi, nhưng truy vấn tương đương này có thể nhanh hơn và nhiều khả năng sử dụng chỉ mục trên (email_recipient_id
) - ngày càng như vậy để có LIMIT
lớn hơn .
SELECT COUNT(*) AS ct
FROM (
SELECT id
FROM email_recipients
WHERE email_campaign_id = 1607
LIMIT 43000
) r
WHERE EXISTS (
SELECT FROM email_activities
WHERE email_recipient_id = r.id);