sql >> Database teknologi >  >> NoSQL >> MongoDB

Kombiner fuldtekst med andet indeks

Hovedsagen her er, at et "tekst"-søgeresultat generelt har forrang over andre filterbetingelser i forespørgslen, og som sådan bliver det nødvendigt at "først" opnå resultater fra "tekst"-komponenten og derefter grundlæggende "scanne" for andre betingelser i dokumentet.

Denne type søgning kan være svær at optimere sammen med en "range" eller en hvilken som helst type "uligheds"-matchbetingelse i forbindelse med tekstsøgeresultaterne, og det skyldes mest, hvordan MongoDB håndterer denne "særlige" indekstype.

Overvej følgende grundlæggende opsætning for en kort demonstration:

db.texty.drop();

db.texty.insert([
    { "a": "a", "text": "something" },
    { "a": "b", "text": "something" },
    { "a": "b", "text": "nothing much" },
    { "a": "c", "text": "something" }
])

db.texty.createIndex({ "text": "text" })
db.texty.createIndex({ "a": 1 })

Så hvis du ville se på dette med en tekstsøgningsbetingelse såvel som en rækkevidde i det andet felt ( { "$lt": "c" } ), så kunne du håndtere som følger:

db.texty.find({ "a": { "$lt": "c" }, "$text": { "$search": "something" } }).explain()

Med forklar-output som (vigtig del):

           "winningPlan" : {
                    "stage" : "FETCH",
                    "filter" : {
                            "a" : {
                                    "$lt" : "c"
                            }
                    },
                    "inputStage" : {
                            "stage" : "TEXT",
                            "indexPrefix" : {

                            },
                            "indexName" : "text_text",
                            "parsedTextQuery" : {
                                    "terms" : [
                                            "someth"
                                    ],
                                    "negatedTerms" : [ ],
                                    "phrases" : [ ],
                                    "negatedPhrases" : [ ]
                            },
                            "inputStage" : {
                                    "stage" : "TEXT_MATCH",
                                    "inputStage" : {
                                            "stage" : "TEXT_OR",
                                            "inputStage" : {
                                                    "stage" : "IXSCAN",
                                                    "keyPattern" : {
                                                            "_fts" : "text",
                                                            "_ftsx" : 1
                                                    },
                                                    "indexName" : "text_text",
                                                    "isMultiKey" : true,
                                                    "isUnique" : false,
                                                    "isSparse" : false,
                                                    "isPartial" : false,
                                                    "indexVersion" : 1,
                                                    "direction" : "backward",
                                                    "indexBounds" : {

                                                    }
                                            }
                                    }
                            }
                    }
            },

Hvilket grundlæggende er at sige "først få mig tekstresultaterne, og filtrer derefter de resultater, der er hentet efter den anden betingelse" . Så det er klart kun "tekst"-indekset, der bliver brugt her, og så bliver alle de resultater, det returnerer, efterfølgende filtreret ved at undersøge indholdet.

Dette er ikke optimalt af to grunde, nemlig at det sandsynligvis kan være, at dataene bedst er begrænset af "område"-betingelsen frem for matchene fra tekstsøgningen. For det andet, selvom der er et indeks på de øvrige data, bliver det ikke brugt her til sammenligning. Så i stedet indlæses hele dokumentet for hvert resultat, og filteret testes.

Du kan så overveje et "sammensat" indeksformat her, og det virker indledningsvis logisk, at hvis "området" er mere specifik for udvælgelse, så inkluder det som præfiksrækkefølgen af ​​de indekserede nøgler:

db.texty.dropIndexes();
db.texty.createIndex({ "a": 1, "text": "text" })

Men der er en hake her, siden når du forsøger at køre forespørgslen igen:

db.texty.find({ "a": { "$lt": "c" }, "$text": { "$search": "something" } })

Det ville resultere i en fejl:

Fejl:fejl:{"waitedMS" :NumberLong(0),"ok" :0,"errmsg" :"fejl ved behandling af forespørgsel:ns=test.textyTree:$and\n a $lt \"c\"\n TEKST:query=noget, language=engelsk, caseSensitive=0, diacriticSensitive=0, tag=NULL\nSorter:{}\nProj:{}\n planlægger returnerede fejl:kunne ikke bruge tekstindeks til at tilfredsstille $text-forespørgsel (hvis tekstindeks er sammensat, er der givet lighedsprædikater for alle præfiksfelter?)","code" :2}

Så selvom det kan virke "optimalt", er den måde, MongoDB behandler forespørgslen på (og egentlig indeksudvælgelse) til det specielle "tekst"-indeks, bare ikke muligt for denne "udelukkelse" uden for intervallet at være mulig.

Du kan dog udføre en "ligestilling" på dette på en meget effektiv måde:

db.texty.find({ "a": "b", "$text": { "$search": "something" } }).explain()

Med forklare-output:

           "winningPlan" : {
                    "stage" : "TEXT",
                    "indexPrefix" : {
                            "a" : "b"
                    },
                    "indexName" : "a_1_text_text",
                    "parsedTextQuery" : {
                            "terms" : [
                                    "someth"
                            ],
                            "negatedTerms" : [ ],
                            "phrases" : [ ],
                            "negatedPhrases" : [ ]
                    },
                    "inputStage" : {
                            "stage" : "TEXT_MATCH",
                            "inputStage" : {
                                    "stage" : "TEXT_OR",
                                    "inputStage" : {
                                            "stage" : "IXSCAN",
                                            "keyPattern" : {
                                                    "a" : 1,
                                                    "_fts" : "text",
                                                    "_ftsx" : 1
                                            },
                                            "indexName" : "a_1_text_text",
                                            "isMultiKey" : true,
                                            "isUnique" : false,
                                            "isSparse" : false,
                                            "isPartial" : false,
                                            "indexVersion" : 1,
                                            "direction" : "backward",
                                            "indexBounds" : {

                                            }
                                    }
                            }
                    }
            },

Så indekset bruges, og det kan vises til at "forfiltrere" det indhold, der leveres til teksten, der matcher output fra den anden betingelse.

Hvis du faktisk beholder "præfikset" til indekset som "tekst" felt(er) for at søge dog:

db.texty.dropIndexes();

db.texty.createIndex({ "text": "text", "a": 1 })

Udfør derefter søgningen:

db.texty.find({ "a": { "$lt": "c" }, "$text": { "$search": "something" } }).explain()

Så ser du et resultat svarende til ovenstående "ligestilling":

            "winningPlan" : {
                    "stage" : "TEXT",
                    "indexPrefix" : {

                    },
                    "indexName" : "text_text_a_1",
                    "parsedTextQuery" : {
                            "terms" : [
                                    "someth"
                            ],
                            "negatedTerms" : [ ],
                            "phrases" : [ ],
                            "negatedPhrases" : [ ]
                    },
                    "inputStage" : {
                            "stage" : "TEXT_MATCH",
                            "inputStage" : {
                                    "stage" : "TEXT_OR",
                                    "filter" : {
                                            "a" : {
                                                    "$lt" : "c"
                                            }
                                    },
                                    "inputStage" : {
                                            "stage" : "IXSCAN",
                                            "keyPattern" : {
                                                    "_fts" : "text",
                                                    "_ftsx" : 1,
                                                    "a" : 1
                                            },
                                            "indexName" : "text_text_a_1",
                                            "isMultiKey" : true,
                                            "isUnique" : false,
                                            "isSparse" : false,
                                            "isPartial" : false,
                                            "indexVersion" : 1,
                                            "direction" : "backward",
                                            "indexBounds" : {

                                            }
                                    }
                            }
                    }
            },

Den store forskel her fra det første forsøg er hvor filter er placeret i behandlingskæden, hvilket indikerer, at selvom det ikke er et "præfiks"-match (hvilket er mest optimalt), bliver indholdet faktisk scannet ud af indekset "før" det sendes til "tekst"-stadiet.

Det er altså "forfiltreret", men selvfølgelig ikke på den mest optimale måde, og det skyldes selve karakteren af, hvordan "tekst"-indekset bruges. Så hvis du lige har overvejet det almindelige område på et indeks for sig selv:

db.texty.createIndex({ "a": 1 })
db.texty.find({ "a": { "$lt": "c" } }).explain()

Derefter forklarer output:

            "winningPlan" : {
                    "stage" : "FETCH",
                    "inputStage" : {
                            "stage" : "IXSCAN",
                            "keyPattern" : {
                                    "a" : 1
                            },
                            "indexName" : "a_1",
                            "isMultiKey" : false,
                            "isUnique" : false,
                            "isSparse" : false,
                            "isPartial" : false,
                            "indexVersion" : 1,
                            "direction" : "forward",
                            "indexBounds" : {
                                    "a" : [
                                            "[\"\", \"c\")"
                                    ]
                            }
                    }
            },

Så fik det i det mindste indexBounds at overveje og kun så på den del af indekset, der faldt inden for disse grænser.

Så det er forskellene her. Brug af en "sammensat" struktur burde spare dig for nogle iterationscyklusser her ved at være i stand til at indsnævre valget, men det skal stadig scanne alle indeksindgange for at filtrere, og det skal selvfølgelig ikke være "præfiks"-elementet i indekset, medmindre du kan bruge et lighedsmatch på det.

Uden en sammensat struktur i indekset returnerer du altid tekstresultaterne "først" og anvender derefter andre betingelser på disse resultater. Det er heller ikke muligt at "kombinere/skære" resultaterne fra at se på et "tekst"-indeks og et "normalt" indeks på grund af forespørgselsmotorens håndtering. Det vil generelt ikke være den optimale tilgang, så planlægning af overvejelser er vigtig.

Kort sagt, ideelt set sammensat med en "lighed" match "præfiks", og hvis ikke, så medtag tekstdefinitionen i indekset "efter".




  1. Brug af flere Mongodb-databaser med Meteor.js

  2. Skal to moduler bruge den samme redis-forbindelse? (Jeg arbejder med Flask)

  3. mongoimport vælge felttype

  4. Top 10 funktioner i Big Data Hadoop