PostgreSQL
 sql >> Cơ Sở Dữ Liệu >  >> RDS >> PostgreSQL

Tại sao một thay đổi nhỏ trong cụm từ tìm kiếm lại làm chậm truy vấn đến vậy?

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 LEFT JOIN . Postgres buộc phải chuyển đổi nó thành 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 :

Cài đặt mặc định cho join_collapse_limit 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.



  1. Database
  2.   
  3. Mysql
  4.   
  5. Oracle
  6.   
  7. Sqlserver
  8.   
  9. PostgreSQL
  10.   
  11. Access
  12.   
  13. SQLite
  14.   
  15. MariaDB
  1. Làm cách nào để làm cho Java và Postgres enums hoạt động cùng nhau để cập nhật?

  2. quyền bị từ chối cố gắng đọc tệp csv bằng JDBC cho cơ sở dữ liệu postgres

  3. .NET Core ghi nhật ký vào PostgreSQL DB bằng NLog

  4. Cách lấy số hàng trong PostgreSQL

  5. Gặp sự cố khi kết hợp HAVING với WHERE trên một QUERY rất đơn giản