การค้นหา Trigram ช้าลงมากเมื่อสตริงการค้นหายาวขึ้น


16

ในฐานข้อมูล Postgres 9.1 ฉันมีตารางที่table1มีแถว ~ 1.5M และคอลัมน์label(ชื่อที่เรียบง่ายเพื่อประโยชน์ของคำถามนี้)

มีดัชนีการทำงานของ Trigram-on lower(unaccent(label)) ( unaccent()ถูกทำให้ไม่เปลี่ยนรูปเพื่ออนุญาตให้ใช้ในดัชนี)

แบบสอบถามต่อไปนี้ค่อนข้างเร็ว:

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

Time: 394,295 ms

แต่แบบสอบถามต่อไปนี้ช้าลง:

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

Time: 1405,749 ms

และการเพิ่มคำอื่น ๆ ก็ช้าลงแม้ว่าการค้นหาจะเข้มงวด

ฉันลองใช้เคล็ดลับง่ายๆในการเรียกใช้เคียวรีย่อยสำหรับคำแรกและจากนั้นเคียวรีที่มีสตริงการค้นหาแบบเต็ม แต่ (เศร้า) ผู้วางแผนเคียวรีเห็นผ่านการเสี้ยวของฉัน:

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%'));
บิตแมป Heap สแกนบนตารางที่ 1 (ราคา = 16216.01..16220.04 แถว = 1 ความกว้าง = 212) (เวลาจริง = 1824.017..1824.019 แถว = 1 ลูป = 1)
  ตรวจสอบอีกครั้ง Cond: ((ต่ำกว่า (ไม่ตอบสนอง ((ป้ายกำกับ) :: ข้อความ)) ~~ '% อุกกาบาต%' :: ข้อความ) และ (ต่ำลง (ไม่ตอบกลับ ((ฉลาก) :: ข้อความ)) ~~ '% Gatord และอีกมากมาย % :: ข้อความ))
  -> ดัชนีบิตแมปสแกนบน table1_label_hun_gin_trgm (ราคา = 0.00..16216.01 แถว = 1 ความกว้าง = 0) (เวลาจริง = 1823.900..1823.900 แถว = 1 ลูป = 1)
        ดัชนี Cond: ((ต่ำกว่า (ไม่ถูกต้อง ((ป้ายกำกับ) :: ข้อความ)) ~~ '% Bernardord%' :: ข้อความ) และ (ต่ำกว่า (ไม่ถูกต้อง ((ป้ายกำกับ) :: ข้อความ)) ~~ '% Gatord และอีกมากมาย % :: ข้อความ))
รันไทม์ทั้งหมด: 1824.064 ms

ปัญหาสุดท้ายของฉันคือสตริงการค้นหามาจากเว็บอินเตอร์เฟสซึ่งอาจส่งสตริงค่อนข้างยาวและช้ามากและอาจเป็นเวกเตอร์ DOS

ดังนั้นคำถามของฉันคือ:

  • จะเพิ่มความเร็วการสืบค้นได้อย่างไร?
  • มีวิธีการแบ่งย่อยเป็นแบบสอบถามย่อยเพื่อให้เร็วขึ้นหรือไม่
  • อาจเป็นรุ่นที่ใหม่กว่าของ Postgres ดีกว่า (ฉันลอง 9.4 และดูเหมือนว่าจะไม่เร็วกว่า: ยังคงมีผลเหมือนเดิมบางทีอาจเป็นรุ่นต่อมาหรือไม่)
  • อาจจำเป็นต้องใช้กลยุทธ์การจัดทำดัชนีที่แตกต่างกันอย่างไร

1
มันจะต้องบอกว่าunaccent()ยังมีให้โดยโมดูลเพิ่มเติมและ Postgres ไม่ได้IMMUTABLEสนับสนุนการจัดทำดัชนีในการทำงานโดยเริ่มต้นตั้งแต่ยังไม่ คุณต้องแก้ไขบางสิ่งและคุณควรพูดถึงสิ่งที่คุณทำในคำถามของคุณ คำแนะนำในการยืนอยู่ของฉัน: stackoverflow.com/a/11007216/939860 นอกจากนี้ดัชนี trigram ยังสนับสนุนการจับคู่แบบตรงตามตัวพิมพ์ใหญ่และตัวพิมพ์เล็ก คุณสามารถลดความซับซ้อนของ: WHERE f_unaccent(label) ILIKE f_unaccent('%someword%')- ด้วยดัชนีที่ตรงกัน รายละเอียด: stackoverflow.com/a/28636000/939860
Erwin Brandstetter

ฉันเพิ่งประกาศunaccentไม่เปลี่ยนรูป ฉันเพิ่มสิ่งนี้ในคำถาม
P.Péter

โปรดทราบว่าแฮ็คนั้นจะถูกเขียนทับเมื่อคุณอัปเดตunaccentโมดูล หนึ่งในเหตุผลที่ฉันแนะนำตัวห่อหุ้มฟังก์ชั่นแทน
Erwin Brandstetter

คำตอบ:


34

ใน PostgreSQL 9.6 จะมี pg_trgm เวอร์ชันใหม่ 1.2 ซึ่งจะดีกว่านี้มาก ด้วยความพยายามเพียงเล็กน้อยคุณสามารถทำให้รุ่นใหม่นี้ทำงานภายใต้ PostgreSQL 9.4 (คุณต้องใช้โปรแกรมปะแก้และรวบรวมโมดูลส่วนขยายด้วยตนเองและติดตั้ง)

สิ่งที่เป็นรุ่นที่เก่าที่สุดคือการค้นหาแต่ละ trigram ในแบบสอบถามและทำการรวมกันของพวกเขาแล้วใช้ตัวกรอง สิ่งที่รุ่นใหม่จะทำคือเลือกทริกเกอร์ที่หายากในการค้นหาและค้นหาเพียงแค่นั้นและจากนั้นกรองที่เหลือในภายหลัง

เครื่องจักรที่ใช้ทำสิ่งนี้ไม่มีอยู่ใน 9.1 ใน 9.4 นั้นมีการเพิ่มเครื่องจักร แต่ pg_trgm ไม่ได้ถูกดัดแปลงเพื่อใช้งานในเวลานั้น

คุณยังคงมีปัญหาเกี่ยวกับ DOS ที่อาจเกิดขึ้นเนื่องจากบุคคลที่ประสงค์ร้ายสามารถสร้างคิวรีที่มีทริกเกอร์ทั่วไปเท่านั้น เช่น '% และ%' หรือแม้แต่ '% a%'


หากคุณไม่สามารถอัปเกรดเป็น pg_trgm 1.2 ได้อีกวิธีในการหลอกลวงนักวางแผนคือ:

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

ด้วยการเชื่อมสตริงว่างเข้ากับฉลากคุณจะหลอกให้ผู้วางแผนคิดว่ามันไม่สามารถใช้ดัชนีในส่วนของประโยคที่ ดังนั้นจึงใช้ดัชนีเป็นเพียง% %ordord% และใช้ตัวกรองกับแถวเหล่านั้น


นอกจากนี้หากคุณค้นหาทั้งคำอยู่เสมอคุณสามารถใช้ฟังก์ชันเพื่อทำเครื่องหมายสตริงให้เป็นอาร์เรย์ของคำและใช้ดัชนี GIN ในตัวปกติ (ไม่ใช่ pg_trgm) ในฟังก์ชันที่ส่งคืนอาร์เรย์นั้น


13
เป็นมูลค่าการกล่าวขวัญว่าคุณเป็นคนที่จะเขียนแพทช์ และการทดสอบประสิทธิภาพเบื้องต้นนั้นน่าประทับใจ สิ่งนี้สมควรได้รับการอัปโหลดมากขึ้น (เช่นสำหรับคำอธิบายและวิธีแก้ปัญหาด้วยเวอร์ชันปัจจุบัน)
Erwin Brandstetter

ฉันจะสนใจอย่างน้อยก็ในการอ้างอิงถึงเครื่องจักรที่คุณใช้ในการติดตั้งโปรแกรมแก้ไขที่ไม่ได้มีในข้อ 9.1 แต่ฉันเห็นด้วยกับคำตอบแย่ ๆ ของ Erwin
Evan Carroll

3

ฉันได้พบวิธีที่จะหลอกลวงนักวางแผนแบบสอบถามมันเป็นแฮ็คที่ค่อนข้างง่าย:

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 เอาท์พุท:

บิตแมป Heap สแกนบนตารางที่ 1 (ราคา = 6749.11..7332.71 แถว = 1 ความกว้าง = 212) (เวลาจริง = 256.607..256.609 แถว = 1 ลูป = 1)
  ตรวจสอบอีกครั้ง Cond: (ต่ำกว่า (ไม่ถูกต้อง ((label_hun) :: ข้อความ)) ~~ '% Gatord%' :: ข้อความ)
  ตัวกรอง: (ต่ำลง (ต่ำลง (ไม่ถูกต้อง ((ป้ายกำกับ :: ข้อความ))))
  -> ดัชนีบิตแมปสแกนบน table1_label_hun_gin_trgm (ราคา = 0.00..6749.11 แถว = 147 ความกว้าง = 0) (เวลาจริง = 256.499..256.499 แถว = 1 ลูป = 1)
        ดัชนี Cond: (ต่ำกว่า (ไม่ถูกต้อง ((ป้ายกำกับ :: ข้อความ)) ~~ '% เฟียร์ดอร์ด%' :: ข้อความ)
รันไทม์ทั้งหมด: 256.653 ms

ดังนั้นเนื่องจากไม่มีดัชนีสำหรับlower(lower(unaccent(label)))สิ่งนี้จะสร้างการสแกนตามลำดับดังนั้นมันจึงกลายเป็นตัวกรองแบบง่าย มีอะไรเพิ่มเติมที่ง่ายและจะทำเช่นเดียวกัน:

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

แน่นอนว่านี่เป็นวิธีแก้ปัญหาที่อาจทำงานได้ไม่ดีนักหากส่วนที่ถูกตัดออกที่ใช้ในการสแกนดัชนีนั้นเป็นเรื่องธรรมดามาก แต่ในฐานข้อมูลของเรามีการทำซ้ำไม่มากถ้าฉันใช้ประมาณ 10-15 ตัวอักษร

มีคำถามสองข้อที่เหลืออยู่:

  • ทำไม postgres ถึงคิดไม่ออกว่าสิ่งนี้จะเป็นประโยชน์?
  • Postgres ทำอะไรในช่วงเวลา 0..256.499 (ดูที่วิเคราะห์เอาต์พุต)

1
ในช่วงเวลาระหว่าง 0 ถึง 256.499 มันกำลังสร้างบิตแมป ที่ 256.499 มันสร้างผลลัพธ์แรกซึ่งเป็นบิตแมป ซึ่งเป็นเอาต์พุตสุดท้ายเนื่องจากสร้างเพียงเอาต์พุตเดียว - บิตแมปที่เสร็จสมบูรณ์เพียงครั้งเดียว
jjanes
โดยการใช้ไซต์ของเรา หมายความว่าคุณได้อ่านและทำความเข้าใจนโยบายคุกกี้และนโยบายความเป็นส่วนตัวของเราแล้ว
Licensed under cc by-sa 3.0 with attribution required.