Arama dizisi uzadıkça trigram arama çok yavaşlar


16

Postgres 9.1 veritabanında, table1~ 1.5M satır ve bir sütun label(bu soru uğruna basitleştirilmiş adlar) içeren bir tablo var .

Üzerinde fonksiyonel bir trigram indeksi vardır lower(unaccent(label))( indekste unaccent()kullanılmasına izin vermek için değişmez hale getirilmiştir).

Aşağıdaki sorgu oldukça hızlıdır:

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

Time: 394,295 ms

Ancak aşağıdaki sorgu daha yavaştır:

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

Time: 1405,749 ms

Ve arama daha katı olmasına rağmen daha fazla kelime eklemek daha da yavaştır.

İlk kelime için bir alt sorgu ve sonra tam arama dizesi ile bir sorgu çalıştırmak için basit bir hile denedim, ama (ne yazık ki) sorgu planlayıcısı benim makineleri ile gördüm:

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%'));
Tablo1'de Bitmap Yığın Taraması (maliyet = 16216.01..16220.04 satır = 1 genişlik = 212) (gerçek zaman = 1824.017..1824.019 satır = 1 döngü = 1)
  Cond: Recheck Cond: ((alt (unaccent ((etiket) :: metin)) ~ ~ '% someword%' :: metin) AND (alt (unaccent ((label) :: metin)) ~~ '% someword ve daha fazlası %'::Metin))
  -> Bitmap Dizini table1_label_hun_gin_trgm'de tarama (maliyet = 0.00..16216.01 satır = 1 genişlik = 0) (gerçek zaman = 1823.900..1823.900 satır = 1 döngü = 1)
        Endeks Koşulu: ((alt (unaccent ((etiket) :: metin)) ~~ '% someword%' :: text) AND (alt (unaccent ((label) :: metin)) ~~ '% someword ve daha fazlası %'::Metin))
Toplam çalışma süresi: 1824.064 ms

Benim en büyük sorunum arama dizesi oldukça uzun dizeleri gönderebilir ve böylece oldukça yavaş olabilir ve aynı zamanda bir DOS vektörü oluşturabilir bir web arayüzü geliyor olmasıdır.

Yani sorularım:

  • Sorgu nasıl hızlandırılır?
  • Daha hızlı olması için alt sorgulara bölmenin bir yolu var mı?
  • Belki Postgres'in sonraki bir sürümü daha iyidir? (9.4'ü denedim ve daha hızlı görünmüyor: hala aynı etki. Belki daha sonraki bir sürüm?)
  • Belki farklı bir indeksleme stratejisine ihtiyaç vardır?

1
O belirtilmelidir unaccent()ayrıca ek modül tarafından sağlanır ve Postgres yok değil o değil çünkü varsayılan olarak işlevini dizinleri desteklemek IMMUTABLE. Bir şeyi değiştirmiş olmalısınız ve sorunuzda tam olarak ne yaptığınızdan bahsetmelisiniz. Daimi tavsiyem: stackoverflow.com/a/11007216/939860 . Ayrıca, trigram dizinleri kutudan büyük / küçük harfe duyarsız eşleşmeyi destekler. Aşağıdakileri basitleştirebilirsiniz: WHERE f_unaccent(label) ILIKE f_unaccent('%someword%')- eşleşen bir dizinle. Ayrıntılar: stackoverflow.com/a/28636000/939860 .
Erwin Brandstetter

Ben sadece unaccentdeğişmez ilan ettim . Bunu soruya ekledim.
P.Péter

unaccentModülü güncellediğinizde bilgisayar korsanlığının üzerine yazıldığını unutmayın . Bunun yerine bir işlev sarmalayıcı önermemin nedenlerinden biri.
Erwin Brandstetter

Yanıtlar:


34

PostgreSQL 9.6'da pg_trgm, 1.2'nin bu konuda çok daha iyi olacak yeni bir sürümü olacak. Biraz çaba sarf ederek, bu yeni sürümü PostgreSQL 9.4 altında da çalıştırabilirsiniz (yamayı uygulamanız ve uzantı modülünü kendiniz derlemeniz ve yüklemeniz gerekir).

En eski sürümün yaptığı, sorgudaki her üçgeni aramak ve birleşimini almak ve ardından bir filtre uygulamaktır. Yeni sürümün yapacağı sorgudaki en nadir üçgeni seçmek ve sadece bunu aramak ve daha sonra geri kalanını filtrelemek.

Bunu yapacak makineler 9.1'de mevcut değildir. 9.4'te bu makineler eklendi, ancak pg_trgm o zamandan faydalanmak için uyarlanmadı.

Kötü niyetli kişi yalnızca ortak trigramları olan bir sorgu oluşturabileceğinden, potansiyel bir DOS sorununuz devam eder. '% ve%' veya hatta '% a%' gibi


Pg_trgm 1.2 sürümüne geçemezseniz, planlayıcıyı kandırmanın başka bir yolu:

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

Etiketlemek için boş dizeyi birleştirerek, planlayıcıyı where yan tümcesinin o kısmındaki dizini kullanamayacağını düşünerek kandırırsınız. Bu nedenle, dizini yalnızca% someword% dizininde kullanır ve yalnızca bu satırlara bir filtre uygular.


Ayrıca, her zaman tüm sözcükleri arıyorsanız, dizeyi bir sözcük dizisine dönüştürmek için bir işlev kullanabilir ve bu dizi döndüren işlevde normal bir yerleşik GIN dizini (pg_trgm değil) kullanabilirsiniz.


13
Yama yazan tek kişi sizsiniz. Ve ön performans testleri etkileyicidir. Bu gerçekten daha fazla oyu hak ediyor (ayrıca mevcut sürümle açıklama ve geçici çözüm için).
Erwin Brandstetter

9.1'de olmayan yamayı uygulamak için kullandığınız makinelere en azından bir referansla daha fazla ilgilenirim. Ancak, Erwin kötü eşek cevap ile katılıyorum
Evan Carroll

3

Ben sorgu planlayıcısı aldatmaca için bir yol bulduk, oldukça basit bir kesmek:

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 çıktı:

Tablo1'deki Bitmap Yığın Taraması (maliyet = 6749.11..7332.71 satır = 1 genişlik = 212) (gerçek zaman = 256.607..256.609 satır = 1 döngü = 1)
  Cond Recheck: (alt (unaccent ((label_hun) :: text)) ~ ~ '% someword%' :: metin)
  Filtre: (alt (alt (alt) ((etiket) :: metin))) ~~ '% someword ve biraz daha%' :: text)
  -> Bitmap Dizini table1_label_hun_gin_trgm'de tarama (maliyet = 0.00..6749.11 satır = 147 genişlik = 0) (gerçek zaman = 256.499..256.499 satır = 1 döngü = 1)
        Endeks Koşulu: (düşük (belirsiz ((etiket) :: metin)) ~~ '% someword%' :: metin)
Toplam çalışma süresi: 256.653 ms

Yani, için bir dizin olmadığından lower(lower(unaccent(label))), bu sıralı bir tarama oluşturacaktır, bu yüzden basit bir filtreye dönüşür. Dahası, basit bir AND de aynı şeyi yapacaktır:

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%'))

Tabii ki, bu endeks taramada kullanılan cut-out parçası ise, iyi çalışmayabilir bir sezgisel olan çok yaygın. Ama veritabanımızda, yaklaşık 10-15 karakter kullanırsam, o kadar çok tekrar yoktur.

Kalan iki küçük soru var:

  • Postgres neden böyle bir şeyin yararlı olacağını anlayamıyor?
  • Postgres 0..256.499 zaman aralığında ne yapar (çıktı analizine bakın)?

1
0 ile 256.499 arasındaki zaman aralığında bitmap oluşturuyor. 256.499 değerinde bitmap olan ilk çıktısını üretir. Bu aynı zamanda son çıktısıdır, çünkü sadece tek bir çıktı üretir - tek tamamlanmış bitmap.
jjanes
Sitemizi kullandığınızda şunları okuyup anladığınızı kabul etmiş olursunuz: Çerez Politikası ve Gizlilik Politikası.
Licensed under cc by-sa 3.0 with attribution required.