Tại sao?
Lý do đây có phải là:
Truy vấn nhanh:
-> Hash Left Join (cost=1378.60..2467.48 rows=15 width=79) (actual time=41.759..85.037 rows=1129 loops=1) ... Filter: (unaccent(((((COALESCE(p.abrev, ''::character varying))::text || ' ('::text) || (COALESCE(p.prenome, ''::character varying))::text) || ')'::text)) ~~* (...)
Truy vấn chậm:
-> Hash Left Join (cost=1378.60..2467.48 rows=1 width=79) (actual time=35.084..80.209 rows=1129 loops=1) ... Filter: (unaccent(((((COALESCE(p.abrev, ''::character varying))::text || ' ('::text) || (COALESCE(p.prenome, ''::character varying))::text) || ')'::text)) ~~* unacc (...)
Việc mở rộng mẫu tìm kiếm theo một ký tự khác khiến Postgres giả định có ít lần truy cập hơn. (Thông thường, đây là một ước tính hợp lý.) Rõ ràng là Postgres không có số liệu thống kê đủ chính xác (thực tế là không có, hãy tiếp tục đọc) để mong đợi cùng một số lần truy cập mà bạn thực sự nhận được.
Điều này gây ra chuyển đổi sang một kế hoạch truy vấn khác, kế hoạch này thậm chí còn kém tối ưu hơn cho thực tế số lần truy cập row =1129
.
Giải pháp
Giả sử Postgres 9.5 hiện tại vì nó chưa được khai báo.
Một cách để cải thiện tình hình là tạo chỉ mục biểu thức vào biểu thức ở vị ngữ. Điều này làm cho Postgres thu thập thống kê cho biểu thức thực tế, có thể giúp truy vấn ngay cả khi bản thân chỉ mục không được sử dụng cho truy vấn . Không có chỉ mục, không có không có thống kê đối với biểu thức. Và nếu được thực hiện đúng, chỉ mục có thể được sử dụng cho truy vấn, điều đó thậm chí còn tốt hơn nhiều. Nhưng có nhiều vấn đề với biểu hiện hiện tại của bạn:
unaccent (TEXT (Coalesce (p.abrev, '') || '(' || Coalesce (p.prenome, '') || ')')) ilike unaccent ('% vicen%' ' )
Hãy xem xét truy vấn cập nhật này, dựa trên một số giả định về các định nghĩa bảng không được tiết lộ của bạn:
SELECT e.id
, (SELECT count(*) FROM imgitem
WHERE tabid = e.id AND tab = 'esp') AS imgs -- count(*) is faster
, e.ano, e.mes, e.dia
, e.ano::text || to_char(e.mes2, 'FM"-"00')
|| to_char(e.dia, 'FM"-"00') AS data
, pl.pltag, e.inpa, e.det, d.ano anodet
, format('%s (%s)', p.abrev, p.prenome) AS determinador
, d.tax
, coalesce(v.val,v.valf) || ' ' || vu.unit AS altura
, coalesce(v1.val,v1.valf) || ' ' || vu1.unit AS dap
, d.fam, tf.nome família, d.gen, tg.nome AS gênero, d.sp
, ts.nome AS espécie, d.inf, e.loc, l.nome localidade, e.lat, e.lon
FROM pess p -- reorder!
JOIN det d ON d.detby = p.id -- INNER JOIN !
LEFT JOIN tax tf ON tf.oldfam = d.fam
LEFT JOIN tax tg ON tg.oldgen = d.gen
LEFT JOIN tax ts ON ts.oldsp = d.sp
LEFT JOIN tax ti ON ti.oldinf = d.inf -- unused, see @joop's comment
LEFT JOIN esp e ON e.det = d.id
LEFT JOIN loc l ON l.id = e.loc
LEFT JOIN var v ON v.esp = e.id AND v.key = 265
LEFT JOIN varunit vu ON vu.id = v.unit
LEFT JOIN var v1 ON v1.esp = e.id AND v1.key = 264
LEFT JOIN varunit vu1 ON vu1.id = v1.unit
LEFT JOIN pl ON pl.id = e.pl
WHERE f_unaccent(p.abrev) ILIKE f_unaccent('%' || 'vicenti' || '%') OR
f_unaccent(p.prenome) ILIKE f_unaccent('%' || 'vicenti' || '%');
Những điểm chính
Tại sao f_unaccent ()
? Bởi vì unaccent ()
không thể được lập chỉ mục. Đọc cái này:
Tôi đã sử dụng hàm được nêu ở đó để cho phép hàm trigram hàm nhiều cột GIN chỉ số sau (được khuyến nghị!) :
CREATE INDEX pess_unaccent_nome_trgm_idx ON pess
USING gin (f_unaccent(pess) gin_trgm_ops, f_unaccent(prenome) gin_trgm_ops);
Nếu bạn không quen với chỉ mục bát quái, hãy đọc phần này trước:
Và có thể:
Đảm bảo chạy phiên bản Postgres mới nhất (hiện tại là 9.5). Đã có những cải tiến đáng kể đối với chỉ số GIN. Và bạn sẽ quan tâm đến những cải tiến trong pg_trgm 1.2, được lên lịch phát hành cùng với Postgres 9.6 sắp tới:
Báo cáo chuẩn bị trước là một cách phổ biến để thực hiện các truy vấn với các tham số (đặc biệt là với văn bản từ đầu vào của người dùng). Postgres phải tìm ra một kế hoạch hoạt động tốt nhất cho bất kỳ tham số nhất định nào. Thêm ký tự đại diện làm hằng số đối với cụm từ tìm kiếm như sau:
f_unaccent(p.abrev) ILIKE f_unaccent('%' || 'vicenti' || '%')
( 'vicenti'
sẽ được thay thế bằng một tham số.) Vì vậy, Postgres biết rằng chúng ta đang xử lý một mẫu không cố định bên trái hoặc bên phải - điều này sẽ cho phép các chiến lược khác nhau. Câu trả lời liên quan với nhiều chi tiết hơn:
Hoặc có thể lập kế hoạch lại truy vấn cho mọi cụm từ tìm kiếm (có thể sử dụng SQL động trong một hàm). Nhưng hãy đảm bảo rằng thời gian lập kế hoạch không làm mất đi bất kỳ mức tăng hiệu suất nào có thể có.
WHERE
điều kiện trên các cột trong pess
mâu thuẫn với . Postgres buộc phải chuyển đổi nó thành LEFT JOIN
INNER JOIN
. Điều tồi tệ hơn là tham gia đến muộn trong cây tham gia. Và vì Postgres không thể sắp xếp lại các liên kết của bạn (xem bên dưới), điều đó có thể trở nên rất tốn kém. Di chuyển bảng đến đầu tiên vị trí trong FROM
mệnh đề loại bỏ hàng sớm. Đang theo dõi LEFT JOIN
s không loại bỏ bất kỳ hàng nào theo định nghĩa. Nhưng với nhiều bảng đó, điều quan trọng là phải di chuyển các phép nối có thể nhân lên hàng cuối cùng.
Bạn đang tham gia 13 bảng, 12 trong số đó bằng THAM GIA TRÁI
cái nào rời khỏi 12!
các kết hợp có thể có - hoặc 11! * 2!
nếu chúng ta lấy một THAM GIA TRÁI
tính đến tài khoản thực sự là một INNER JOIN
. Đó là quá nhiều cho Postgres để đánh giá tất cả các hoán vị có thể có cho kế hoạch truy vấn tốt nhất. Đọc về join_collapse_limit
:
- Truy vấn mẫu để hiển thị lỗi ước lượng Cardinality trong PostgreSQL
- SQL INNER JOIN trên nhiều bảng bằng cú pháp WHERE
Cài đặt mặc định cho join_collapse_limit
là 8 , có nghĩa là Postgres sẽ không cố gắng sắp xếp lại các bảng trong FROM
của bạn mệnh đề và thứ tự của các bảng là có liên quan .
Một cách giải quyết vấn đề này là chia phần quan trọng về hiệu suất thành CTE
như @ joop đã nhận xét
. Không đặt join_collapse_limit
cao hơn nhiều hoặc thời gian lập kế hoạch truy vấn liên quan đến nhiều bảng được kết hợp sẽ xấu đi.
Giới thiệu về ngày kết hợp của bạn dữ liệu được đặt tên
:
ép kiểu (ép kiểu (e.ano dưới dạng varchar (4)) || '-' || ngay ('0' || ép kiểu (e.mes dưới dạng varchar (2)), 2) || ' - '|| right (' 0 '|| ép kiểu (e.dia as varchar (2)), 2) as varchar (10)) dưới dạng dữ liệu
Giả định bạn tạo từ ba cột số cho năm, tháng và ngày, được xác định NOT NULL
, sử dụng cái này thay thế:
e.ano::text || to_char(e.mes2, 'FM"-"00')
|| to_char(e.dia, 'FM"-"00') AS data
Giới thiệu về FM
công cụ sửa đổi mẫu mẫu:
Nhưng thực sự, bạn nên lưu trữ ngày dưới dạng dữ liệu kiểu date
để bắt đầu.
Cũng được đơn giản hóa:
format('%s (%s)', p.abrev, p.prenome) AS determinador
Sẽ không làm cho truy vấn nhanh hơn, nhưng nó gọn gàng hơn nhiều. Xem format ()
.
Điều đầu tiên cuối cùng, tất cả lời khuyên thông thường cho tối ưu hóa hiệu suất áp dụng:
Nếu bạn làm đúng tất cả điều này, bạn sẽ thấy các truy vấn nhanh hơn nhiều cho tất cả các mẫu.