Tìm kiếm trigram trở nên chậm hơn nhiều vì chuỗi tìm kiếm dài hơn


16

Trong cơ sở dữ liệu Postgres 9.1, tôi có một bảng table1có ~ 1,5 triệu hàng và một cột label(tên đơn giản cho mục đích của câu hỏi này).

Có một chỉ số trigram chức năng trên lower(unaccent(label))( unaccent()đã được thực hiện bất biến để cho phép sử dụng nó trong chỉ mục).

Truy vấn sau đây khá nhanh:

SELECT count(*) FROM table1
WHERE (lower(unaccent(label)) like lower(unaccent('%someword%')));
 count 
-------
     1
(1 row)

Time: 394,295 ms

Nhưng truy vấn sau chậm hơn:

SELECT count(*) FROM table1
WHERE (lower(unaccent(label)) like lower(unaccent('%someword and some more%')));
 count 
-------
     1
(1 row)

Time: 1405,749 ms

Và thêm nhiều từ thậm chí còn chậm hơn, mặc dù tìm kiếm chặt chẽ hơn.

Tôi đã thử một mẹo đơn giản để chạy một truy vấn con cho từ đầu tiên và sau đó là một truy vấn với chuỗi tìm kiếm đầy đủ, nhưng (đáng buồn thay) trình hoạch định truy vấn đã thấy qua các mưu mô của tôi:

EXPLAIN ANALYZE
SELECT * FROM (
   SELECT id, title, label from table1
   WHERE lower(unaccent(label)) like lower(unaccent('%someword%'))
   ) t1
WHERE lower(unaccent(label)) like lower(unaccent('%someword and some more%'));
Bitmap Heap Scan trên bảng1 (chi phí = 16216,01..16220.04 hàng = 1 width = 212) (thời gian thực tế = 1824.017..1824.019 hàng = 1 vòng = 1)
  Kiểm tra lại Cond: ((thấp hơn (không có dấu ((nhãn) :: văn bản)) ~ ~ '% Somethingord%' :: văn bản) VÀ (thấp hơn (không có dấu ((nhãn) :: văn bản)) ~ ~ '% Somethingord và một số chi tiết khác %'::bản văn))
  -> Quét chỉ mục Bitmap trên bảng1_label_hun_gin_trgm (chi phí = 0,00..16216,01 hàng = 1 width = 0) (thời gian thực tế = 1823.900..1823.900 hàng = 1 vòng = 1)
        Chỉ mục Cond: ((thấp hơn (không có dấu ( %'::bản văn))
Tổng thời gian chạy: 1824.064 ms

Vấn đề cuối cùng của tôi là chuỗi tìm kiếm đến từ một giao diện web có thể gửi các chuỗi khá dài và do đó khá chậm và cũng có thể tạo thành một vectơ DOS.

Vì vậy, câu hỏi của tôi là:

  • Làm thế nào để tăng tốc độ truy vấn?
  • Có cách nào để chia nó thành các truy vấn con để nó nhanh hơn không?
  • Có lẽ một phiên bản sau của Postgres là tốt hơn? (Tôi đã thử 9,4 và có vẻ không nhanh hơn: vẫn có hiệu ứng tương tự. Có lẽ là phiên bản mới hơn?)
  • Có lẽ một chiến lược lập chỉ mục khác nhau là cần thiết?

1
Nó phải được đề cập unaccent()cũng được cung cấp bởi một mô-đun bổ sung và Postgres không hỗ trợ các chỉ mục trên chức năng theo mặc định vì nó không phải là IMMUTABLE. Bạn phải thay đổi một cái gì đó và bạn nên đề cập đến những gì bạn đã làm chính xác trong câu hỏi của bạn. Lời khuyên thường trực của tôi: stackoverflow.com/a/11007216/939860 . Ngoài ra, các chỉ số trigram hỗ trợ khớp không phân biệt chữ hoa chữ thường. Bạn có thể đơn giản hóa để: WHERE f_unaccent(label) ILIKE f_unaccent('%someword%')- với một chỉ mục phù hợp. Chi tiết: stackoverflow.com/a/28636000/939860 .
Erwin Brandstetter

Tôi chỉ đơn giản là tuyên bố unaccentbất biến. Tôi đã thêm điều này vào câu hỏi.
P.Péter

Xin lưu ý rằng bản hack bị ghi đè khi bạn cập nhật unaccentmô-đun. Thay vào đó, một trong những lý do tại sao tôi đề xuất một trình bao bọc hàm.
Erwin Brandstetter

Câu trả lời:


34

Trong PostgreSQL 9.6 sẽ có phiên bản mới của pg_trgm, 1.2, sẽ tốt hơn nhiều về điều này. Với một chút nỗ lực, bạn cũng có thể khiến phiên bản mới này hoạt động trong PostgreQuery 9.4 (bạn phải áp dụng bản vá và tự biên dịch mô-đun mở rộng và cài đặt nó).

Những gì phiên bản cũ nhất làm là tìm kiếm từng bát quái trong truy vấn và lấy liên kết của chúng, sau đó áp dụng bộ lọc. Những gì phiên bản mới sẽ làm là chọn bát quái hiếm nhất trong truy vấn và tìm kiếm chỉ một cái đó, sau đó lọc phần còn lại sau đó.

Các máy móc để làm điều này không tồn tại trong 9.1. Trong 9,4 máy móc đó đã được thêm vào, nhưng pg_trgm không thích nghi để sử dụng nó vào thời điểm đó.

Bạn vẫn có thể gặp sự cố DOS tiềm ẩn do người độc hại có thể tạo một truy vấn chỉ có các bát quái chung. như '% và%', hoặc thậm chí '% a%'


Nếu bạn không thể nâng cấp lên pg_trgm 1.2, thì một cách khác để lừa người lập kế hoạch sẽ là:

WHERE (lower(unaccent(label)) like lower(unaccent('%someword%'))) 
AND   (lower(unaccent(label||'')) like 
      lower(unaccent('%someword and some more%')));

Bằng cách nối chuỗi rỗng vào nhãn, bạn lừa người lập kế hoạch nghĩ rằng nó không thể sử dụng chỉ mục trên phần đó của mệnh đề where. Vì vậy, nó sử dụng chỉ mục trên chỉ %%ordord% và áp dụng bộ lọc cho chỉ những hàng đó.


Ngoài ra, nếu bạn luôn tìm kiếm toàn bộ từ, bạn có thể sử dụng hàm để mã hóa chuỗi thành một mảng từ và sử dụng chỉ mục GIN tích hợp thông thường (không phải pg_trgm) trên hàm trả về mảng đó.


13
Đáng nói là bạn là người viết bản vá. Và thử nghiệm hiệu suất sơ bộ là ấn tượng. Điều này thực sự xứng đáng được nâng cấp nhiều hơn (cũng cho lời giải thích và giải pháp với phiên bản hiện tại).
Erwin Brandstetter

Tôi sẽ quan tâm nhiều hơn đến ít nhất là một tài liệu tham khảo về máy móc bạn đã sử dụng để thực hiện bản vá không có trong 9.1. Nhưng, tôi đồng ý với câu trả lời mông xấu của Erwin.
Evan Carroll

3

Tôi đã tìm thấy một cách để lừa đảo kế hoạch truy vấn, đó là một cách hack khá đơn giản:

SELECT *
FROM (
   select id, title, label
   from   table1
   where  lower(unaccent(label)) like lower(unaccent('%someword%'))
   ) t1
WHERE lower(lower(unaccent(label))) like lower(unaccent('%someword and more%'))

EXPLAIN đầu ra:

Bitmap Heap Scan trên bảng1 (chi phí = 6749.11..7332.71 hàng = 1 width = 212) (thời gian thực tế = 256.607..256.609 hàng = 1 vòng = 1)
  Kiểm tra lại Cond: (thấp hơn (không có dấu ((nhãn_hun) :: văn bản)) ~ ~ '% Somethingord%' :: text)
  Bộ lọc: (thấp hơn (thấp hơn (không có dấu ((nhãn) :: văn bản))) ~ ~ '% Somethingord và một số%' :: text khác)
  -> Quét chỉ mục Bitmap trên bảng1_label_hun_gin_trgm (chi phí = 0,00..6749.11 hàng = 147 width = 0) (thời gian thực tế = 256.499..256.499 hàng = 1 vòng = 1)
        Chỉ số Cond: (thấp hơn (không có dấu ((nhãn) :: văn bản)) ~ ~ '% Somethingord%' :: text)
Tổng thời gian chạy: 256.653 ms

Vì vậy, vì không có chỉ mục cho lower(lower(unaccent(label))), điều này sẽ tạo ra một lần quét liên tiếp, do đó, nó được chuyển thành một bộ lọc đơn giản. Hơn nữa, một AND đơn giản cũng sẽ làm tương tự:

SELECT id, title, label
FROM table1
WHERE lower(unaccent(label)) like lower(unaccent('%someword%'))
AND   lower(lower(unaccent(label))) like lower(unaccent('%someword and more%'))

Tất nhiên, đây là một heuristic có thể không hoạt động tốt, nếu phần cut-out được sử dụng trong quét chỉ mục là rất phổ biến. Nhưng trong cơ sở dữ liệu của chúng tôi, thực sự không có nhiều sự lặp lại, nếu tôi sử dụng khoảng 10-15 ký tự.

Có hai câu hỏi nhỏ còn lại:

  • Tại sao postgres không thể hiểu rằng một cái gì đó như thế này sẽ có ích?
  • Postgres làm gì trong khoảng thời gian 0..256.499 (xem phân tích đầu ra)?

1
Trong phạm vi thời gian từ 0 đến 256.499, nó đang xây dựng bitmap. Tại 256.499, nó tạo ra đầu ra đầu tiên, đó là bitmap. Đây cũng là đầu ra cuối cùng của nó, vì nó chỉ tạo ra một đầu ra duy nhất - một bitmap hoàn thành duy nhất.
jjanes
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.