PostgreSQL LIKE các biến thể hiệu suất truy vấn


112

Tôi đã thấy sự thay đổi khá lớn về thời gian phản hồi liên quan đến LIKEcác truy vấn tới một bảng cụ thể trong cơ sở dữ liệu của mình. Đôi khi tôi sẽ nhận được kết quả trong vòng 200-400 mili giây (rất chấp nhận được) nhưng những lần khác, có thể mất tới 30 giây để trả về kết quả.

Tôi hiểu rằng LIKEcác truy vấn rất tốn tài nguyên nhưng tôi không hiểu tại sao lại có sự khác biệt lớn như vậy về thời gian phản hồi. Tôi đã xây dựng một chỉ mục btree trên owner1trường nhưng tôi không nghĩ rằng nó giúp ích cho LIKEcác truy vấn. Ai có ý tưởng gì không?

SQL mẫu:

SELECT gid, owner1 FORM parcels
WHERE owner1 ILIKE '%someones name%' LIMIT 10

Tôi cũng đã thử:

SELECT gid, owner1 FROM parcels
WHERE lower(owner1) LIKE lower('%someones name%') LIMIT 10

Và:

SELECT gid, owner1 FROM parcels
WHERE lower(owner1) LIKE lower('someones name%') LIMIT 10

Với kết quả tương tự.
Số hàng trên bảng: khoảng 95.000.

Câu trả lời:


281

FTS không hỗ trợ LIKE

Các câu trả lời được chấp nhận trước đây là không chính xác. Tìm kiếm toàn văn bản với các chỉ mục văn bản đầy đủ của nó hoàn toàn không dành cho LIKEtoán tử, nó có các toán tử riêng và không hoạt động với các chuỗi tùy ý. Nó hoạt động dựa trên các từ dựa trên từ điển và gốc. Nó không hỗ trợ phù hợp với tiền tố cho lời , nhưng không phải với các LIKEnhà điều hành:

Chỉ mục Trigram cho LIKE

Cài đặt mô-đun bổ sung pg_trgmcung cấp các lớp toán tử cho các chỉ mục GIN và GiST trigram để hỗ trợ tất cả LIKEILIKEcác mẫu , không chỉ các mẫu được neo bên trái:

Chỉ mục ví dụ:

CREATE INDEX tbl_col_gin_trgm_idx  ON tbl USING gin  (col gin_trgm_ops);

Hoặc là:

CREATE INDEX tbl_col_gist_trgm_idx ON tbl USING gist (col gist_trgm_ops);

Truy vấn mẫu:

SELECT * FROM tbl WHERE col LIKE '%foo%';   -- leading wildcard
SELECT * FROM tbl WHERE col ILIKE '%foo%';  -- works case insensitively as well

Hình bát quái? Còn về các chuỗi ngắn hơn?

Các từ có ít hơn 3 chữ cái trong các giá trị được lập chỉ mục vẫn hoạt động. Hướng dẫn sử dụng:

Mỗi từ được coi là có hai dấu cách được đặt trước và một dấu cách được đặt trước khi xác định bộ bát quái có trong chuỗi.

Và các mẫu tìm kiếm có ít hơn 3 chữ cái? Hướng dẫn sử dụng:

Đối với cả LIKEtìm kiếm biểu thức chính quy và tìm kiếm biểu thức chính quy, hãy nhớ rằng một mẫu không có bát quái có thể trích xuất sẽ chuyển thành quét toàn bộ chỉ mục.

Có nghĩa là, việc quét chỉ mục index / bitmap vẫn hoạt động (các kế hoạch truy vấn cho câu lệnh đã chuẩn bị sẽ không bị hỏng), nó sẽ không giúp bạn có hiệu suất tốt hơn. Thông thường, không có tổn thất lớn nào, vì các chuỗi 1 hoặc 2 chữ cái hầu như không được chọn lọc (hơn một vài phần trăm các kết quả phù hợp với bảng bên dưới) và hỗ trợ chỉ mục sẽ không cải thiện hiệu suất ngay từ đầu, vì quá trình quét toàn bộ bảng nhanh hơn.


text_pattern_ops để đối sánh tiền tố

Đối với chỉ các mẫu được neo bên trái (không có ký tự đại diện ở đầu), bạn sẽ có được giá trị tối ưu với lớp toán tử phù hợp cho chỉ mục btree: text_pattern_opshoặc varchar_pattern_ops. Cả hai tính năng tích hợp của Postgres tiêu chuẩn, không cần mô-đun bổ sung. Hiệu suất tương tự, nhưng chỉ số nhỏ hơn nhiều.

Chỉ mục ví dụ:

CREATE INDEX tbl_col_text_pattern_ops_idx ON tbl(col text_pattern_ops);

Truy vấn mẫu:

SELECT * FROM tbl WHERE col LIKE 'foo%';  -- no leading wildcard

Hoặc , nếu bạn nên chạy cơ sở dữ liệu của mình với ngôn ngữ 'C' (thực sự không có ngôn ngữ), thì mọi thứ vẫn được sắp xếp theo thứ tự byte và chỉ mục btree thuần túy với lớp toán tử mặc định sẽ thực hiện công việc.

Thêm chi tiết, giải thích, ví dụ và liên kết trong các câu trả lời liên quan này trên dba.SE:


Khi không có ký tự đại diện hàng đầu trên bảng 500K dòng, chỉ số gin với gin_trgm_ops xuất hiện là nhanh hơn so với btree 10 lần
nicolas

@nicolas: Việc so sánh phụ thuộc vào nhiều biến. Độ dài khóa, phân phối dữ liệu, độ dài mẫu, chỉ số có thể quét ... Và quan trọng nhất: Phiên bản Postgres. Chỉ số GIN đã được cải thiện đáng kể trong trang 9,4 và 9,5. Phiên bản mới của pg_trgm (sẽ được phát hành với pg 9.6) sẽ mang lại nhiều cải tiến hơn.
Erwin Brandstetter

1
Nếu tôi hiểu đúng tài liệu, pg_trgmbạn cần chuỗi truy vấn có độ dài ít nhất 3 ký tự, ví dụ: fo%sẽ không đánh chỉ mục nhưng thay vào đó hãy quét. Vài điều cần lưu ý.
Tuukka Mustonen

1
@TuukkaMustonen: Điểm tốt. Chà, tính năng quét chỉ mục (bitmap) vẫn hoạt động , chúng chỉ không mang lại cho bạn hiệu suất tốt hơn. Tôi đã thêm một số làm rõ ở trên.
Erwin Brandstetter

7

Có thể những cái nhanh là những mẫu cố định có phân biệt chữ hoa chữ thường như vậy có thể sử dụng chỉ mục. tức là không có thẻ đại diện ở đầu chuỗi khớp để người thực thi có thể sử dụng quét phạm vi chỉ mục. ( nhận xét có liên quan trong tài liệu ở đây ) Thấp hơn và ilike cũng sẽ mất khả năng sử dụng chỉ mục của bạn trừ khi bạn tạo chỉ mục cụ thể cho mục đích đó (xem chỉ mục chức năng ).

Nếu bạn muốn tìm kiếm chuỗi ở giữa trường, bạn nên xem các chỉ mục toàn văn hoặc chỉ mục bát quái . Đầu tiên trong số chúng nằm trong lõi Postgres, phần còn lại có sẵn trong các mô-đun đóng góp.


Tôi đã không nghĩ đến việc tạo một chỉ mục trên giá trị chữ thường của trường. Bằng cách đó, tôi có thể chuyển đổi văn bản truy vấn thành chữ thường trên phụ trợ trước khi truy vấn.
Jason

4

Bạn có thể cài đặt Wildspeed , một loại chỉ mục khác trong PostgreSQL. Wildspeed hoạt động với ký tự đại diện% word%, không vấn đề gì. Nhược điểm là kích thước của chỉ mục, điều này có thể lớn, rất lớn.


3

Vui lòng Thực hiện truy vấn được đề cập bên dưới để cải thiện hiệu suất truy vấn LIKE trong postgresql. tạo một chỉ mục như thế này cho các bảng lớn hơn:

CREATE INDEX <indexname> ON <tablename> USING btree (<fieldname> text_pattern_ops)

Điều này chỉ hoạt động nếu mẫu không bắt đầu bằng ký tự đại diện - trong trường hợp này, cả hai truy vấn mẫu đầu tiên đều bắt đầu bằng ký tự đại diện.
cbz

1

đối với những gì nó đáng giá, Django ORM có xu hướng sử dụng UPPER(text)cho tất cả các LIKEtruy vấn để phân biệt chữ hoa chữ thường,

Thêm một chỉ mục vào UPPER(column::text)đã tăng tốc đáng kể hệ thống của tôi, không giống như bất kỳ thứ nào khác.

Theo như% dẫn đầu, có, điều đó sẽ không sử dụng một chỉ mục. Xem blog này để có lời giải thích tuyệt vời:

https://use-the-index-luke.com/sql/where-clause/searching-for-ranges/like-performance-tuning


1

Gần đây tôi đã gặp sự cố tương tự với bảng chứa 200000 bản ghi và tôi cần thực hiện các truy vấn LIKE lặp lại. Trong trường hợp của tôi, chuỗi đang được tìm kiếm đã được sửa. Các lĩnh vực khác đa dạng. Bởi vì điều đó, tôi đã có thể viết lại:

SELECT owner1 FROM parcels
WHERE lower(owner1) LIKE lower('%someones name%');

như

CREATE INDEX ix_parcels ON parcels(position(lower('someones name') in lower(owner1)));

SELECT owner1 FROM parcels
WHERE position(lower('someones name') in lower(owner1)) > 0;

Tôi rất vui khi các truy vấn trở lại nhanh chóng và xác minh rằng chỉ mục đang được sử dụng với EXPLAIN ANALYZE:

 Bitmap Heap Scan on parcels  (cost=7.66..25.59 rows=453 width=32) (actual time=0.006..0.006 rows=0 loops=1)
   Recheck Cond: ("position"(lower(owner1), 'someones name'::text) > 0)
   ->  Bitmap Index Scan on ix_parcels  (cost=0.00..7.55 rows=453 width=0) (actual time=0.004..0.004 rows=0 loops=1)
         Index Cond: ("position"(lower(owner1), 'someones name'::text) > 0)
 Planning time: 0.075 ms
 Execution time: 0.025 ms

0

Các truy vấn thích của bạn có thể không thể sử dụng các chỉ mục bạn đã tạo vì:

1) tiêu chí LIKE của bạn bắt đầu bằng ký tự đại diện.

2) bạn đã sử dụng một chức năng với tiêu chí LIKE của bạn.


0

Khi nào bạn sử dụng một mệnh đề trên một cột có các chức năng như LIKE, ILIKE, upper, under, v.v. Khi đó postgres sẽ không xem xét chỉ mục bình thường của bạn. Nó sẽ quét toàn bộ bảng qua từng hàng và do đó nó sẽ chậm.

Cách đúng sẽ là tạo một chỉ mục mới theo truy vấn của bạn. Ví dụ: nếu tôi muốn so khớp một cột không có phân biệt chữ hoa chữ thường và cột của tôi là một varchar. Sau đó, bạn có thể làm điều đó như thế này.

create index ix_tblname_col_upper on tblname (UPPER(col) varchar_pattern_ops);

Tương tự nếu cột của bạn là một văn bản thì bạn làm như thế này

create index ix_tblname_col_upper on tblname (UPPER(col) text_pattern_ops);

Tương tự, bạn có thể thay đổi chức năng trên thành bất kỳ chức năng nào khác mà bạn muốn.

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.