Dalam database Postgres 9.1, saya memiliki tabel table1
dengan ~ 1,5 juta baris dan kolom label
(nama yang disederhanakan untuk pertanyaan ini).
Ada trigram-indeks fungsional pada lower(unaccent(label))
( unaccent()
telah dibuat tidak dapat diubah untuk memungkinkan penggunaannya dalam indeks).
Permintaan berikut ini cukup cepat:
SELECT count(*) FROM table1
WHERE (lower(unaccent(label)) like lower(unaccent('%someword%')));
count
-------
1
(1 row)
Time: 394,295 ms
Tetapi kueri berikut lebih lambat:
SELECT count(*) FROM table1
WHERE (lower(unaccent(label)) like lower(unaccent('%someword and some more%')));
count
-------
1
(1 row)
Time: 1405,749 ms
Dan menambahkan lebih banyak kata bahkan lebih lambat, meskipun pencariannya lebih ketat.
Saya mencoba trik sederhana untuk menjalankan subquery untuk kata pertama dan kemudian kueri dengan string pencarian lengkap, tetapi (sayangnya) perencana kueri melihat melalui intrik saya:
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 pada table1 (biaya = 16216.01..16220.04 baris = 1 lebar = 212) (waktu aktual = 1824.017..1824.019 baris = 1 loop = 1) Periksa kembali Cond: ((lebih rendah (unaccent ((label) :: text)) ~~ '% anyord%' :: text) AND (lebih rendah (unaccent ((label) :: text)) ~~ '% sortord dan beberapa lagi %'::teks)) -> Pemindaian Indeks Bitmap pada table1_label_hun_gin_trgm (biaya = 0,00..16216,01 baris = 1 lebar = 0) (waktu aktual = 1823.900..1823.900 baris = 1 putaran = 1) Indeks Cond: ((lebih rendah (unaccent ((label) :: text)) ~~ '% anyord%' :: text) AND (lebih rendah (unaccent ((label) :: text)) ~~ '% anyord dan beberapa lagi %'::teks)) Total runtime: 1824.064 ms
Masalah utama saya adalah bahwa string pencarian berasal dari antarmuka web yang dapat mengirim string yang cukup panjang dan karenanya sangat lambat dan juga merupakan vektor DOS.
Jadi pertanyaan saya adalah:
- Bagaimana cara mempercepat kueri?
- Apakah ada cara untuk memecahnya menjadi subqueries sehingga lebih cepat?
- Mungkin versi Postgres yang lebih baru lebih baik? (Saya mencoba 9,4 dan sepertinya tidak lebih cepat: masih efek yang sama. Mungkin versi yang lebih baru?)
- Mungkin diperlukan strategi pengindeksan yang berbeda?
unaccent()
ini juga disediakan oleh modul tambahan dan Postgres tidak mendukung indeks pada fungsi secara default karena tidakIMMUTABLE
. Anda harus mengubah sesuatu dan Anda harus menyebutkan apa yang Anda lakukan persis dalam pertanyaan Anda. Saran berdiri saya: stackoverflow.com/a/11007216/939860 . Juga, indeks trigram mendukung pencocokan case-sensitive di luar kotak. Anda dapat menyederhanakan untuk:WHERE f_unaccent(label) ILIKE f_unaccent('%someword%')
- dengan indeks yang cocok. Detail: stackoverflow.com/a/28636000/939860 .unaccent
abadi. Saya menambahkan ini ke pertanyaan.unaccent
modul. Salah satu alasan mengapa saya menyarankan pembungkus fungsi sebagai gantinya.Jawaban:
Di PostgreSQL 9.6 akan ada versi baru pg_trgm, 1.2, yang akan jauh lebih baik tentang ini. Dengan sedikit usaha, Anda juga bisa membuat versi baru ini berfungsi di bawah PostgreSQL 9.4 (Anda harus menerapkan tambalan, dan mengkompilasi modul ekstensi sendiri dan menginstalnya).
Apa yang dilakukan versi tertua adalah mencari setiap trigram dalam kueri dan mengambil gabungannya, lalu menerapkan filter. Apa yang versi baru akan lakukan adalah memilih trigram yang paling langka dalam kueri dan mencari yang itu, dan kemudian menyaring sisanya.
Mesin untuk melakukan ini tidak ada dalam 9.1. Di 9.4 bahwa mesin ditambahkan, tetapi pg_trgm tidak diadaptasi untuk menggunakannya saat itu.
Anda masih memiliki potensi masalah DOS, karena orang jahat dapat membuat kueri yang hanya memiliki trigram umum. seperti '% dan%', atau bahkan '% a%'
Jika Anda tidak dapat memutakhirkan ke pg_trgm 1.2, maka cara lain untuk mengelabui perencana adalah:
Dengan menggabungkan string kosong ke label, Anda menipu perencana untuk berpikir itu tidak dapat menggunakan indeks pada bagian mana klausa. Jadi ia menggunakan indeks pada% anyord%, dan menerapkan filter untuk hanya baris-baris itu.
Juga, jika Anda selalu mencari seluruh kata, Anda bisa menggunakan fungsi untuk tokenize string ke dalam array kata, dan menggunakan indeks GIN built-in biasa (bukan pg_trgm) pada fungsi yang mengembalikan array.
sumber
Saya telah menemukan cara untuk menipu perencana kueri, ini adalah hack yang cukup sederhana:
EXPLAIN
keluaran:Jadi, karena tidak ada indeks untuk
lower(lower(unaccent(label)))
, ini akan membuat pemindaian berurutan, sehingga akan berubah menjadi filter sederhana. Terlebih lagi, sederhana DAN juga akan melakukan hal yang sama:Tentu saja, ini adalah heuristik yang mungkin tidak berfungsi dengan baik, jika bagian cut-out yang digunakan dalam pemindaian indeks sangat umum. Tetapi dalam database kami, tidak ada pengulangan yang terlalu banyak, jika saya menggunakan sekitar 10-15 karakter.
Ada dua pertanyaan kecil yang tersisa:
sumber