Căutarea full-text în MongoDB

MongoDB, una dintre cele mai importante baze de date NoSQL, este binecunoscută pentru performanța sa rapidă, schema flexibilă, scalabilitatea și capacitățile excelente de indexare. În centrul acestei performanțe rapide se află indicii MongoDB, care sprijină executarea eficientă a interogărilor prin evitarea scanărilor complete și prin urmare limitarea numărului de căutări ale documentelor MongoDB. 

Pornind de la versiunea 2.4, MongoDB a început cu ajutorul unei caracteristici experimentale Căutarea în întregul text utilizând Indicii de text. Această caracteristică a devenit acum o parte integrantă a produsului (și nu mai este o caracteristică experimentală). În acest articol vom explora funcționalitățile full-text de căutare ale MongoDB chiar de la fundamentale.

Dacă sunteți nou la MongoDB, vă recomand să citiți următoarele articole despre Envato Tuts + care vă vor ajuta să înțelegeți conceptele de bază ale MongoDB:

  • Noțiuni de bază cu MongoDB - Partea 1
  • Maparea bazelor de date relaționale și SQL pentru MongoDB 

Cele elementare

Înainte de a intra în detalii, să ne uităm la un anumit fundal. Căutarea full-text se referă la tehnica de căutare a text complet de date în funcție de criteriile de căutare specificate de utilizator. Este ceva asemănător cu modul în care căutăm orice conținut de pe Google (sau de fapt orice altă aplicație de căutare) prin introducerea anumitor cuvinte cheie / fraze și ștergerea rezultatelor relevante sortate după clasarea lor.

Iată câteva scenarii în care se va întâmpla o căutare de text întreg:

  • Luați în considerare căutarea subiectului preferat pe Wiki. Când introduceți un text de căutare pe Wiki, motorul de căutare aduce rezultatele tuturor articolelor legate de cuvintele cheie / expresia pe care ați căutat-o ​​(chiar dacă acele cuvinte cheie au fost folosite adânc în interiorul articolului). Aceste rezultate ale căutării sunt sortate după relevanță pe baza scorului lor corespunzător.  
  • Ca un alt exemplu, luați în considerare un site de rețea socială unde utilizatorul poate face o căutare pentru a găsi toate postările care conțin cuvântul cheie pisiciîn ele; sau să fie mai complexe, toate posturile care au comentarii care conțin cuvântul pisici.  

Înainte de a merge mai departe, există anumiți termeni generali referitori la căutarea full-text pe care ar trebui să o cunoașteți. Acești termeni se aplică oricărei implementări de căutare fulltext (și nu MongoDB specifice).

Opriți cuvintele

Cuvintele stop sunt cuvintele irelevante care trebuie filtrate dintr-un text. De exemplu: a, un, este, la, care, etc.

Pentru a putea reduce

Stemming este procesul de reducere a cuvintelor la tulpina lor. De exemplu: cuvinte cum ar fi picioarele, standurile, picioarele etc. au un stand comun de bază.

scoring

O clasare relativă pentru a măsura care dintre rezultatele căutării este cea mai relevantă.  

Alternative la căutarea full-text în MongoDB

Înainte ca MongoDB să vină cu conceptul de indexuri de text, vom modela datele noastre pentru a sprijini căutările de cuvinte cheie sau pentru a folosi expresii regulate pentru implementarea unor astfel de funcționalități de căutare. Cu toate acestea, folosirea oricăreia dintre aceste abordări a avut propriile limitări:

  • În primul rând, niciuna dintre aceste abordări nu sprijină funcționalități cum ar fi stoparea, stoparea cuvintelor, clasarea etc.  
  • Utilizarea căutărilor de cuvinte cheie ar necesita crearea de indici multi-cheie, care nu sunt suficiente în comparație cu textul întreg.
  • Folosirea expresiilor regulate nu este eficientă din punctul de vedere al performanței, deoarece aceste expresii nu utilizează efectiv indici.
  • În plus, niciuna dintre aceste tehnici nu poate fi utilizată pentru a efectua căutări de expresii (cum ar fi căutarea pentru "filme lansate în 2015") sau căutări ponderate.  

În afară de aceste abordări, pentru aplicații mai avansate și mai complexe în funcție de căutare, există soluții alternative cum ar fi Elastic Search sau SOLR. Dar folosind oricare dintre aceste soluții crește complexitatea arhitecturală a aplicației, deoarece MongoDB trebuie să discute cu o bază de date externă suplimentară. 

Rețineți că căutarea integrală a textului de către MongoDB nu este propusă ca o înlocuire completă a bazelor de date ale motoarelor de căutare, cum ar fi Elastic, SOLR etc. Cu toate acestea, aceasta poate fi utilizată în mod eficient pentru majoritatea aplicațiilor construite astăzi cu MongoDB.

Introducerea textului MongoDB

Folosind căutarea full-text MongoDB, puteți defini un index text pe orice câmp din document a cărui valoare este un șir sau un șir de șiruri de caractere. Atunci când creăm un index text pe un câmp, MongoDB tokenizes și sfârșește conținutul textului câmpului indexat și stabilește indexurile în mod corespunzător.  

Pentru a înțelege lucrurile în continuare, să ne aruncăm acum câteva lucruri practice. Vreau să urmați tutorialul cu mine încercând exemplele din coajă mongo. Vom crea mai întâi câteva exemple de date pe care le vom folosi în întregul articol, apoi vom trece mai departe pentru a discuta conceptele cheie.

În sensul prezentului articol, luați în considerare o colecție mesaje care stochează documente cu următoarea structură: 

"subiect": "Joe posedă un câine", "conținut": "Câinii sunt cel mai bun prieten al omului", "place": 60, "an": 2015, "language": "english"

Să introducem câteva exemple de documente folosind introduce comandați-ne pentru a crea datele noastre de testare:

db.messages.insert ("subiect": "Joe posedă un câine", "conținut": "Câinii sunt cel mai bun prieten al omului", "place": 60, "anul": 2015, "limba": engleză " ) db.messages.insert ("subject": "Câini mănâncă pisici și câini mănâncă și porumbei", "content": "Pisicile nu sunt rele", "likes": 30, "year": 2015; "englezesc") db.messages.insert ("subiect": "Pisici mănâncă șobolani", "conținut": "Șobolani nu gătesc alimente", "place": 55; "Engleză") db.messages.insert ("subiect": "Șobolani mănâncă Joe", "conținut": "Joe a mâncat un șobolan" Engleză")

Crearea unui index text

Se creează un index text similar cu modul în care creăm un index regulat, cu excepția faptului că specifică text în loc să specificați o ordine ascendentă / descendentă.

Indexarea unui câmp unic

Creați un index text pe subiect câmpul documentului nostru folosind următoarea interogare:

db.messages.createIndex ( "subiect": "text")

Pentru a testa acest index nou creat pe subiect câmp, vom căuta documente folosind $ Text operator. Vom căuta toate documentele care au cuvântul cheie câini în lor subiect camp. 

Din moment ce efectuăm o căutare text, suntem, de asemenea, interesați de obținerea unor statistici despre cât de relevante sunt documentele rezultate. În acest scop, vom folosi $ Meta: "textScore" expresie, care oferă informații despre prelucrarea $ Text operator. De asemenea, vom sorta documentele prin intermediul lor textScore folosind fel comanda. O mai mare textScore indică o potrivire mai relevantă. 

db.messages.find ($ text: $ search: "câini", scor: $ meta: "toextScore") sortați (scor: $ meta: "textScore")

Interogarea de mai sus returnează următoarele documente care conțin cuvântul cheie câini în lor subiect camp. 

"_id": ObjectId ("55f4a5d9b592880356441e94"), "subject": "Câini mănâncă pisici și câini mănâncă și porumbei", "content": " "câinele este cel mai bun prieten al omului", "câinele este cel mai bun prieten al omului", " place ": 60," an ": 2015," limbă ":" engleză "," scor ": 0.6666666666666666

După cum puteți vedea, primul document are un scor de 1 (de la cuvântul cheie câine apare de două ori în subiectul său), spre deosebire de cel de-al doilea document cu un scor de 0,66. De asemenea, interogarea a sortat documentele returnate în ordinea descrescătoare a scorului lor.

O singură întrebare care ar putea apărea în mintea dvs. este că dacă căutăm cuvântul cheie câini, de ce este motorul de căutare este de luare a cuvântului cheie câine (fără "s")? Amintiți-vă discuția noastră cu privire la stopming, în cazul în care orice cuvinte cheie de căutare sunt reduse la baza lor? Acesta este motivul pentru care cuvântul cheie câini este redus la câine.

Indexarea câmpurilor multiple (Indexarea compușilor)

De cele mai multe ori, veți utiliza căutarea pe mai multe câmpuri ale unui document. În exemplul nostru, vom permite indexarea textului compus pe subiect și conţinut câmpuri. Mergeți mai departe și executați următoarea comandă în shell mongo:  

db.messages.createIndex ( "subiect": "text", "conținut": "text")

A funcționat? Nu!! Crearea unui al doilea index de text vă va oferi un mesaj de eroare care spune că există deja un index de căutare în format întreg. De ce este așa? Răspunsul este că indiciile de text vin cu o limitare a unui singur index de text per colecție. Prin urmare, dacă doriți să creați un alt index de text, va trebui să renunțați la cel existent și să creați unul nou. 

db.messages.dropIndex ("subject_text") db.messages.createIndex ("subiect": "text", "conținut": "text" 

După ce ați executat interogările de creare a indexului de mai sus, încercați să căutați toate documentele cu cuvântul cheie pisică.

db.messages.find ($ text: $ search: "cat", scor: $ meta: "textScore") sort ($ meta: "textScore")

Interogarea de mai sus va afișa următoarele documente:

"_id": ObjectId ("55f4af22b592880356441ea4"), "subject": "Câini mănâncă pisici și câine mănâncă și porumbei", "content": " "limba": "engleză", "scor": "1.3333333333333335 " _id ": ObjectId (" 55f4af22b592880356441ea5 ")," subject ":" Pisici mănâncă șobolani " ": 55," an ": 2014," limbă ":" engleză "," scor ": 0.6666666666666666 

Puteți observa scorul primului document, care conține cuvântul cheie pisică în ambele subiect și conţinut câmpuri, este mai mare. 

Indexarea întregului document (Indexarea cu Wildcard)

În ultimul exemplu, am pus un index combinat pe subiect și conţinut câmpuri. Dar pot exista scenarii în care doriți ca orice document text din documentele dvs. să poată fi căutat. 

De exemplu, luați în considerare stocarea e-mailurilor în documentele MongoDB. În cazul e-mailurilor, toate câmpurile, inclusiv expeditorul, destinatarul, subiectul și corpul, trebuie să poată fi căutate. În astfel de scenarii, puteți să indexați toate câmpurile de caractere ale documentului folosind $ ** indicatorul wildcard.

Interogarea ar merge așa (asigurați-vă că ștergeți indicele existent înainte de a crea unul nou):

db.messages.createIndex ( "$ **": "text")

Această interogare ar crea automat indexuri de text pe orice câmp de șir din documentele noastre. Pentru a testa acest lucru, introduceți un nou document cu un câmp nou Locație în el:

db.messages.insert ("subiect": "Păsările pot găti", "conținut": "Păsările nu mănâncă șobolani", "place": 12, "an" :"Engleză")

Acum, dacă încercați să căutați text cu cuvântul cheie chicago (interogare de mai jos), va returna documentul pe care tocmai l-am inserat.

db.messages.find ($ text: $ search: "chicago", scor: $ meta: "textScore") sortați (scor: $ meta: "textScore")

Cateva lucruri la care as vrea sa ma concentrez aici:

  • Observați că nu am definit în mod explicit un index pe Locație după introducerea unui nou document. Acest lucru se datorează faptului că am definit deja un index text pe întregul document folosind $ ** operator.
  • Indicii wildcard poate fi lent uneori, în special în scenariile în care datele dvs. sunt foarte mari. Din acest motiv, planificați cu înțelepciune indexurile documentului (aka wild cards indexes), deoarece acestea pot provoca o lovitură de performanță.

Căutare avansată

Căutare frază

Puteți căuta expresii precum "păsările inteligente care iubesc gătitul"folosind indexuri de text. În mod implicit, expresia de căutare face o SAU căutați în toate cuvintele cheie specificate, adică va căuta documente care conțin fie cuvintele cheie inteligent, pasăre, dragoste sau bucătar.

db.messages.find ($ text: $ search: "păsări inteligente care gătesc", scor: $ meta: "scor text") sortare (scor: $ meta: „)

Această interogare va afișa următoarele documente:

"_id": ObjectId ("55f5289cb592880356441ead"), "subiect": "Păsările pot găti", "conținut": "Păsările nu mănâncă șobolani"; "Chicago", "limbă": "engleză", "scor": 2 "_id": ObjectId ("55f5289bb592880356441eab"), "subject": " "," place ": 55," an ": 2014," limbă ":" engleză "," scor ": 0.6666666666666666 

În cazul în care doriți să efectuați o căutare frază exactă (logică ȘI), puteți face acest lucru specificând citate duble în textul de căutare. 

db.messages.find ($ text: $ search: "\" cook food \ "", scor: $ meta: "textScore") sort (scor: $ meta: „)

Această interogare ar duce la următorul document care conține fraza "bucate de mâncare" împreună:

"_id": ObjectId ("55f5289bb592880356441eab"), "subject": "Pisici mănâncă șobolani", "conținut": "Șobolani nu gătesc alimente"; "engleză", "scor": 0.6666666666666666

Căutarea prin negociere

Prefixarea unui cuvânt cheie de căutare cu - (semnul minus) exclude toate documentele care conțin termenul negat. De exemplu, încercați să căutați orice document care conține cuvântul cheie şobolan dar nu conține păsări utilizând următoarea interogare:

db.messages.find ($ text: $ search: "rat -birds", scor: $ meta: "textScore") sortați (scor: $ meta: "textScore" )

Privind în spatele scenelor

O funcționalitate importantă pe care nu am dezvăluit-o până acum este modul în care te uiți în spatele scenei și vezi cum sunt descoperite cuvintele dvs. cheie de căutare, opriți formularea aplicată, negată etc.. $ explica la salvare. Puteți rula interogarea de explicație prin trecere Adevărat ca parametru, care vă va oferi statistici detaliate privind executarea interogării.  

db.messages.find ($ text: $ căutare: "câini care nu mănâncă pisici" - câini mănâncă "prietenii", scor: $ meta: "textScore" scor: $ meta: "textScore".) explică (adevărat) 

Dacă te uiți la queryPlanner obiect returnat de comanda explică, veți putea vedea modul în care MongoDB analizează șirul de căutare dat. Observați că a neglijat cuvinte de oprire precum care, și a izbucnit câini la câine

Puteți vedea, de asemenea, termenii pe care i-am neglijat din căutarea noastră și frazele pe care le-am folosit în parsedTextQuery secțiune.  

"parsedTextQuery": "terms": ["câine", "pisică", "nu", "mâncare", "mâncat", "șobolan", "câine" "]," fraze ": [" câinii mănâncă "]," negatPrazii ": [] 

Interogarea explicată va fi foarte utilă pe măsură ce executăm interogări de căutare mai complexe și doriți să le analizați.

Căutare text căutate

Când în documentul nostru avem indicii pe mai multe câmpuri, de cele mai multe ori un câmp va fi mai important (adică mai multă greutate) decât celălalt. De exemplu, atunci când căutați pe un blog, titlul blogului ar trebui să aibă o greutate maximă, urmat de conținutul blogului.

Greutatea prestabilită pentru fiecare câmp indexat este 1. Pentru a aloca greutăți relative pentru câmpurile indexate, puteți include greutăți în timpul utilizării createIndex comanda.

Să înțelegem acest lucru cu un exemplu. Dacă încercați să căutați bucătar cuvântul cheie cu indicii actuali, va rezulta în două documente, ambele având același punctaj.   

db.messages.find ($ text: $ search: "cook", scor: $ meta: "textScore") sortați (scor: $ meta: "textScore")
"_id": ObjectId ("55f5289cb592880356441ead"), "subiect": "Păsările pot găti", "conținut": "Păsările nu mănâncă șobolani"; "Chicago", "limbă": "engleză", "scor": 0.6666666666666666 "_id": ObjectId ("55f5289bb592880356441eab"), "subject": " "," place ": 55," an ": 2014," limbă ":" engleză "," scor ": 0.6666666666666666 

Acum permiteți-ne să modificăm indicii pentru a include greutăți; cu subiect câmp având o greutate de 3 împotriva conţinut câmp având o greutate de 1.

db.messages.createIndex ("$ **": "text", "greutăți": subiect: 3, conținut: 1)

Încercați să căutați un cuvânt cheie bucătar acum, și veți vedea că documentul care conține acest cuvânt cheie în subiect câmpul are un scor mai mare (de 2) decât celălalt (care are 0.66).

"_id": ObjectId ("55f5289cb592880356441ead"), "subiect": "Păsările pot găti", "conținut": "Păsările nu mănâncă șobolani"; "Chicago", "limbă": "engleză", "scor": 2 "_id": ObjectId ("55f5289bb592880356441eab"), "subject": " "," place ": 55," an ": 2014," limbă ":" engleză "," scor ": 0.6666666666666666 

Indici de text de partiționare

Pe măsură ce datele stocate în aplicația dvs. cresc, mărimea indexurilor de text continuă să crească. Cu această creștere a dimensiunii indexurilor de text, MongoDB trebuie să caute toate intrările indexate ori de câte ori este efectuată o căutare text. 

Ca o tehnică care vă permite să vă păstrați eficiența căutării textului cu indicii în creștere, puteți limita numărul de intrări scanate indexate utilizând condițiile de egalitate cu o regulă $ Text căutare. Un exemplu foarte comun al acestui lucru ar fi căutarea tuturor postărilor făcute într-un anumit an / lună sau căutarea tuturor postărilor cu o anumită categorie / etichetă.

Dacă observați documentele pe care lucrăm, avem un a an câmpul în care nu am folosit încă. Un scenariu comun ar fi căutarea de mesaje pe an, împreună cu căutarea full-text pe care am învățat-o. 

Pentru aceasta, putem crea un index compus care specifică o cheie index ascendentă / descendentă an urmat de un index text pe subiect camp. Făcând acest lucru, facem două lucruri importante:

  • Partiționăm logic întreaga colecție de date în seturi separate pe an.
  • Acest lucru ar limita căutarea textului pentru a scana numai acele documente care se încadrează într-un anumit an (sau o numiți setată).

Aruncați indiciile pe care le aveți deja și creați un nou index pe (an, subiect):

db.messages.createIndex ("an": 1, "subiect": "text")

Acum, executați următoarea interogare pentru a căuta toate mesajele create în 2015 și pentru a le conține pisici cuvinte cheie:

db.messages.find (anul: 2015, text $: $ search: "pisici", scor: $ meta: "textScore" )

Interogarea ar întoarce un singur document potrivit așa cum era de așteptat. daca tu explica această interogare și uita-te la executionStats, veți găsi asta totalDocsExamined pentru că această interogare a fost 1, ceea ce confirmă faptul că noul nostru index a fost utilizat corect, iar MongoDB a trebuit să scaneze doar un singur document ignorând în siguranță toate celelalte documente care nu se încadrează în 2015.

Indicii de text: Beneficii

Ce mai pot face indexurile de text?

Am parcurs un drum lung în acest articol de învățare despre indici de text. Există multe alte concepte pe care le puteți experimenta cu indici de text. Dar datorită domeniului de aplicare al acestui articol, nu vom putea să le discutăm în detaliu astăzi. Cu toate acestea, să aruncăm o scurtă privire asupra a ceea ce sunt aceste funcționalități:

  • Indicii de text oferă suport multi-lingvistic, permițându-vă să căutați în diferite limbi utilizând limbă $ operator. MongoDB susține în prezent în jur de 15 limbi, inclusiv franceză, germană, rusă etc.
  • Indicii de text pot fi utilizați în interogările de agregare ale conductelor. Stadiul de potrivire într-o căutare agregată poate specifica utilizarea unei interogări de căutare completă.
  • Puteți utiliza operatorii obișnuiți pentru proiecții, filtre, limite, tipuri, etc., în timp ce lucrați cu indexuri de text.

MongoDB Text Indexation vs Baze de date de căutare externă

Ținând cont de faptul că căutarea full-text MongoDB nu este o înlocuire completă a bazelor de date tradiționale ale motoarelor de căutare utilizate cu MongoDB, se recomandă utilizarea funcționalității native MongoDB din următoarele motive:

  • Ca pe o discuție recentă la MongoDB, scopul actual al căutării de text funcționează perfect pentru o majoritate de aplicații (în jur de 80%) care sunt construite folosind MongoDB astăzi.
  • Construirea capabilităților de căutare a aplicației dvs. în cadrul aceleiași baze de date a aplicațiilor reduce complexitatea arhitecturală a aplicației.
  • Căutarea de text MongoDB funcționează în timp real, fără niciun fel de întârziere sau actualizare batch. În momentul în care inserați sau actualizați un document, intrările de index de text sunt actualizate.
  • Căutarea textului fiind integrată în funcționalitățile kernelului db ale MongoDB, este total consecventă și funcționează bine chiar și cu sharpening și replicare.
  • Se integrează perfect cu funcțiile Mongo existente, cum ar fi filtrele, agregarea, actualizările etc..    

Indicii de text: dezavantaje

Căutarea integrală a textului este o caracteristică relativ nouă în MongoDB, existând anumite funcționalități care îi lipsesc în prezent. Le-aș împărți în trei categorii. Haideți să aruncăm o privire.

Funcțiile lipsesc din căutarea textului

  • În prezent, indicele de text nu are capacitatea de a suporta interfețe pluggable, cum ar fi semnalele pluggable, cuvinte stop, etc.
  • Ele nu acceptă în prezent funcții precum căutarea bazată pe sinonime, cuvinte similare etc..
  • Ei nu stochează poziții termale, adică numărul de cuvinte prin care cele două cuvinte cheie sunt separate.
  • Nu puteți specifica ordinea de sortare pentru o expresie de sortare dintr-un index de text.

Restricții în funcționalitățile existente

  • Un index de text compus nu poate include nici un alt tip de index, cum ar fi indexurile multi-cheie sau indexurile geospațiale. În plus, dacă indexul de text compus include orice chei de index înainte de cheia indexului de text, toate interogările trebuie să specifice operatorii de egalitate pentru tastele precedente.
  • Există câteva limitări specifice interogării. De exemplu, o interogare poate specifica doar o singură interogare $ Text expresie, nu puteți folosi $ Text cu $ și nici, nu puteți folosi aluzie() comanda cu $ Text, utilizând $ Text cu $ sau are nevoie de toate clauzele din textul tău $ sau expresia care trebuie indexată etc.

Performanță Dezavantaje

  • Indicii de text creează un cost suplimentar în timp ce introduc noi documente. Aceasta, la rândul său, atinge capacitatea de introducere.
  • Unele interogări precum căutările de expresii pot fi relativ lente.

Înfășurarea în sus 

Căutarea integrală a textului a fost întotdeauna una dintre cele mai solicitate funcții ale MongoDB. În acest articol, am început cu o introducere a ceea ce este căutarea full-text, înainte de a trece la elementele de bază ale creării de indici de text. 

Apoi am explorat indexarea compușilor, indexarea wildcard-urilor, căutări de expresii și căutări de negare. Mai mult, am explorat câteva concepte importante, cum ar fi analizarea indexurilor de text, căutarea ponderată și partiționarea logică a indexurilor. Ne putem aștepta la unele actualizări majore ale acestei funcționalități în versiunile viitoare ale MongoDB. 

Vă recomandăm să încercați să căutați în text și să vă împărtășiți gândurile. Dacă l-ați implementat deja în aplicația dvs., împărtășiți-vă cu amabilitate experiența dvs. aici. În cele din urmă, nu ezitați să postați întrebările, gândurile și sugestiile pe acest articol în secțiunea de comentarii.  

Cod