sql >> Databasteknik >  >> NoSQL >> MongoDB

Kombinera fulltext med annat index

Huvudfallet här är att ett "text"-sökresultat i allmänhet har företräde framför andra filtervillkor i frågan, och som sådant blir det nödvändigt att "först" få resultat från "text"-komponenten och sedan i princip "skanna" för övriga villkor i dokumentet.

Denna typ av sökning kan vara svår att optimera tillsammans med ett "intervall" eller någon typ av "ojämlikhet" matchningsvillkor i samband med textsökresultaten, och beror mest på hur MongoDB hanterar denna "speciella" indextyp.

För en kort demonstration, överväg följande grundläggande inställningar:

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å om du ville titta på detta med ett textsökvillkor samt en intervallövervägande i det andra fältet ( { "$lt": "c" } ), då kan du hantera enligt följande:

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

Med förklara-utgången som (viktig 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" : {

                                                    }
                                            }
                                    }
                            }
                    }
            },

Vilket i princip är att säga "först skaffa mig textresultaten och filtrera sedan de resultat som hämtas med det andra villkoret" . Så det är uppenbart att bara "text"-indexet används här och sedan filtreras alla resultat som det returnerar genom att granska innehållet.

Detta är inte optimalt av två skäl, nämligen att det sannolikt kan vara så att data bäst begränsas av "intervall"-villkoret snarare än matchningarna från textsökningen. För det andra, även om det finns ett index på de andra uppgifterna, används det inte här för jämförelse. Så istället laddas hela dokumentet för varje resultat och filtret testas.

Du kan då överväga ett "sammansatt" indexformat här, och det verkar till en början logiskt att om "intervallet" är mer specifikt för val, inkludera det som prefixordningen för de indexerade nycklarna:

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

Men det finns en hake här, sedan när du försöker köra frågan igen:

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

Det skulle resultera i ett fel:

Error:error:{"waitedMS" :NumberLong(0),"ok" :0,"errmsg" :"fel vid bearbetning av fråga:ns=test.textyTree:$and\n a $lt \"c\"\n TEXT :query=något, language=engelska, caseSensitive=0, diacriticSensitive=0, tag=NULL\nSortera:{}\nProj:{}\n planerare returnerade fel:misslyckades med att använda textindex för att tillfredsställa $text-frågan (om textindex är sammansatt, ges likhetspredikat för alla prefixfält?)","code" :2}

Så även om det kan verka "optimalt", hur MongoDB bearbetar frågan (och egentligen indexval) för det speciella "text"-indexet, är det helt enkelt inte möjligt att denna "uteslutning" utanför intervallet är möjlig.

Du kan dock utföra en "jämlikhetsmatchning" på detta på ett mycket effektivt sätt:

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

Med förklara-utgången:

           "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å indexet används och det kan visas för att "förfiltrera" innehållet till texten som matchar resultatet av det andra villkoret.

Om du verkligen behåller "prefixet" till indexet som "text"-fält för att söka dock:

db.texty.dropIndexes();

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

Utför sedan sökningen:

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

Då ser du ett resultat som liknar ovanstående "jämlikhet"-matchning:

            "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 stora skillnaden här från första försöket är där filter placeras i bearbetningskedjan, vilket indikerar att även om det inte är en "prefix"-matchning (vilket är mest optimalt), så skannas innehållet verkligen bort från indexet "innan" det skickas till "text"-stadiet.

Så det är "förfiltrerat" men naturligtvis inte på det mest optimala sättet, och detta beror på själva karaktären av hur "text"-indexet används. Så om du bara övervägde det vanliga intervallet på ett index för sig:

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

Sedan förklarar utdata:

            "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\")"
                                    ]
                            }
                    }
            },

Då fick det åtminstone indexBounds att överväga och bara tittade på den del av indexet som föll inom dessa gränser.

Så det är skillnaderna här. Genom att använda en "sammansatt" struktur bör du spara några iterationscykler här genom att kunna begränsa urvalet, men det måste fortfarande skanna alla indexposter för att filtrera, och får naturligtvis inte vara "prefix"-elementet i indexet om du inte kan använda en likhetsmatchning på det.

Utan en sammansatt struktur i indexet returnerar du alltid textresultaten "först" och tillämpar sedan andra villkor på dessa resultat. Det är inte heller möjligt att "kombinera/korsa" resultaten från att titta på ett "text"-index och ett "normalt" index på grund av sökmotorns hantering. Det kommer i allmänhet inte att vara det optimala tillvägagångssättet, så det är viktigt att planera för överväganden.

Kort sagt, helst sammansatt med en "likhet" matchar "prefix", och om inte, inkludera i indexet "efter" textdefinitionen.




  1. Hur man lagrar aggregerade katalogträdsökresultat i Redis

  2. Hur man kör råa mongodb-kommandon från pymongo

  3. Hur ställer jag in ett elasticache redis-kluster som slav?

  4. Java-klient för att ansluta ElasticCache Redis Cache Node