sql >> Database >  >> NoSQL >> MongoDB

Combineer volledige tekst met andere index

Het belangrijkste geval hier is dat een "tekst"-zoekresultaat over het algemeen voorrang heeft op andere filtervoorwaarden in de zoekopdracht, en als zodanig wordt het noodzakelijk om "eerst" resultaten te verkrijgen van de "tekst"-component, en dan in feite te "scannen" naar andere voorwaarden in het document.

Dit type zoekopdracht kan moeilijk te optimaliseren zijn, samen met een "bereik" of een ander type "ongelijkheid"-overeenkomst in combinatie met de tekstzoekresultaten, en is grotendeels te wijten aan de manier waarop MongoDB met dit "speciale" indextype omgaat.

Overweeg voor een korte demonstratie de volgende basisinstellingen:

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 })

Dus als u dit wilt bekijken met een tekstzoekvoorwaarde en een bereikoverweging in het andere veld ( { "$lt": "c" } ), dan zou je het als volgt kunnen aanpakken:

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

Met de uitvoer van de uitleg zoals ( belangrijk deel ):

           "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" : {

                                                    }
                                            }
                                    }
                            }
                    }
            },

Dat is in feite zeggen "eerst geef me de tekstresultaten en filter die resultaten die zijn opgehaald door de andere voorwaarde" . Het is dus duidelijk dat hier alleen de "tekst" -index wordt gebruikt en dat alle resultaten die het oplevert vervolgens worden gefilterd door de inhoud te onderzoeken.

Dit is om twee redenen niet optimaal, namelijk dat het waarschijnlijk is dat de gegevens het beste worden beperkt door de voorwaarde "bereik" in plaats van de overeenkomsten uit de tekstzoekopdracht. Ten tweede, hoewel er een index is op de andere gegevens, wordt deze hier niet gebruikt ter vergelijking. Dus liever wordt het hele document voor elk resultaat geladen en wordt het filter getest.

Je zou hier dan een "samengestelde" indexindeling kunnen overwegen, en het lijkt in eerste instantie logisch dat als het "bereik" specifieker is voor selectie, dat dan als de prefix-volgorde van de geïndexeerde sleutels opneemt:

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

Maar hier zit een addertje onder het gras, want als je de query opnieuw probeert uit te voeren:

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

Het zou resulteren in een fout:

Error:error:{"waitedMS" :NumberLong(0),"ok" :0,"errmsg" :"fout bij het verwerken van query:ns=test.textyTree:$and\n a $lt \"c\"\n TEKST:query=something, language=english, caseSensitive=0, diacriticSensitive=0, tag=NULL\nSorteren:{}\nProj:{}\n planner heeft fout geretourneerd:kan tekstindex niet gebruiken om te voldoen aan $text-query (als tekstindex is samengesteld, worden gelijkheidspredikaten gegeven voor alle prefixvelden?)","code" :2}

Dus ook al lijkt dat "optimaal", de manier waarop MongoDB de query verwerkt (en eigenlijk de indexselectie) voor de speciale "tekst" -index, het is gewoon niet mogelijk dat deze "uitsluiting" buiten het bereik mogelijk is.

U kunt hier echter op een zeer efficiënte manier een "gelijkheids" wedstrijd op uitvoeren:

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

Met de uitvoer uitleggen:

           "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" : {

                                            }
                                    }
                            }
                    }
            },

Dus de index wordt gebruikt en het kan worden getoond om de inhoud te "voorfilteren" die aan de tekst wordt geleverd die overeenkomt met de uitvoer van de andere voorwaarde.

Als u inderdaad het "voorvoegsel" voor de index behoudt als de "tekst"-veld(en) om te zoeken:

db.texty.dropIndexes();

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

Voer vervolgens de zoekopdracht uit:

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

Dan zie je een vergelijkbaar resultaat als de bovenstaande "gelijkheids" match:

            "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" : {

                                            }
                                    }
                            }
                    }
            },

Het grote verschil hier met de eerste poging is waar filter wordt in de verwerkingsketen geplaatst, wat aangeeft dat hoewel het geen "prefix"-overeenkomst is (wat het meest optimaal is), de inhoud inderdaad van de index wordt gescand "voordat" wordt verzonden naar het "tekst" -stadium.

Het is dus "voorgefilterd", maar natuurlijk niet op de meest optimale manier, en dit komt door de aard van de manier waarop de "tekst" -index wordt gebruikt. Dus als je alleen het gewone bereik op een index alleen hebt overwogen:

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

Dan de uitvoer van de uitleg:

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

Dan kreeg dat tenminste de indexBounds om te overwegen en alleen naar dat deel van de index te kijken dat binnen die grenzen viel.

Dus dat zijn de verschillen hier. Het gebruik van een "samengestelde" structuur zou u hier enkele iteratiecycli moeten besparen door de selectie te kunnen verfijnen, maar het moet nog steeds alle indexitems scannen om te filteren, en moet natuurlijk niet het "prefix"-element in de index zijn, tenzij je er een gelijkheidsovereenkomst op kunt gebruiken.

Zonder een samengestelde structuur in de index, retourneert u de tekstresultaten altijd "eerst" en past u vervolgens eventuele andere voorwaarden toe op die resultaten. Het is ook niet mogelijk om de resultaten te "combineren/doorsnijden" door naar een "tekst"-index en een "normale" index te kijken vanwege de verwerking van de query-engine. Dat zal over het algemeen niet de optimale aanpak zijn, dus het is belangrijk om overwegingen te plannen.

Kortom, idealiter samengesteld met een "gelijkheid" match "prefix", en zo niet, dan opnemen in de index "na" de tekstdefinitie.




  1. Webscraping en crawlen met Scrapy en MongoDB

  2. Wat is de standaard sessietime-out en hoe deze te configureren bij gebruik van de Spring Session met Redis als backend

  3. Upsert Array Elements die voldoen aan criteria in een MongoDB-document?

  4. Leid de uitvoer van de mongo-query om naar een csv-bestand