MongoDB
 sql >> Datenbank >  >> NoSQL >> MongoDB

Volltext mit anderem Index kombinieren

Der Hauptfall hier ist, dass ein "Text"-Suchergebnis im Allgemeinen Vorrang vor anderen Filterbedingungen in der Abfrage hat und es daher notwendig wird, "zuerst" Ergebnisse aus der "Text"-Komponente zu erhalten und dann im Grunde danach zu "scannen". andere Bedingungen im Dokument.

Diese Art der Suche kann schwierig zu optimieren sein, zusammen mit einem „Bereich“ oder jeder Art von „Ungleichheit“-Übereinstimmungsbedingung in Verbindung mit den Textsuchergebnissen, und liegt hauptsächlich daran, wie MongoDB diesen „speziellen“ Indextyp handhabt.

Betrachten Sie für eine kurze Demonstration die folgende grundlegende Einrichtung:

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

Wenn Sie dies also mit einer Textsuchbedingung sowie einer Bereichsbetrachtung für das andere Feld betrachten möchten ( { "$lt": "c" } ), dann könnten Sie wie folgt vorgehen:

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

Mit der EXPLAIN-Ausgabe wie (wichtiger Teil):

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

                                                    }
                                            }
                                    }
                            }
                    }
            },

Was im Grunde bedeutet "zuerst Holen Sie sich die Textergebnisse und filtern Sie dann die Ergebnisse, die von der anderen Bedingung abgerufen wurden." . Hier wird also eindeutig nur der "Text"-Index verwendet und dann werden alle Ergebnisse, die er zurückgibt, anschließend gefiltert, indem der Inhalt untersucht wird.

Dies ist aus zwei Gründen nicht optimal, da die Daten wahrscheinlich am besten durch die "Bereichs"-Bedingung und nicht durch die Übereinstimmungen aus der Textsuche eingeschränkt werden. Zweitens, obwohl es einen Index zu den anderen Daten gibt, wird dieser hier nicht zum Vergleich verwendet. Es wird also für jedes Ergebnis das ganze Dokument geladen und der Filter getestet.

Sie könnten hier dann ein "zusammengesetztes" Indexformat in Betracht ziehen, und es erscheint zunächst logisch, dass, wenn der "Bereich" spezifischer für die Auswahl ist, dies als vorangestellte Reihenfolge der indizierten Schlüssel eingeschlossen wird:

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

Aber hier gibt es einen Haken, seit wann Sie versuchen, die Abfrage erneut auszuführen:

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

Es würde zu einem Fehler führen:

Fehler:error:{"waitedMS" :NumberLong(0),"ok" :0,"errmsg" :"error processing query:ns=test.textyTree:$and\n a $lt \"c\"\n TEXT :query=something, language=english, caseSensitive=0, diacriticSensitive=0, tag=NULL\nSort:{}\nProj:{}\n Planer hat einen Fehler zurückgegeben:konnte den Textindex nicht verwenden, um die $text-Abfrage zu erfüllen (wenn der Textindex zusammengesetzt, sind für alle Präfixfelder Gleichheitsprädikate angegeben?)","code" :2}

Auch wenn dies "optimal" erscheinen mag, so wie MongoDB die Abfrage (und wirklich die Indexauswahl) für den speziellen "Text"-Index verarbeitet, ist es einfach nicht möglich, dass dieser "Ausschluss" außerhalb des Bereichs möglich ist.

Sie können jedoch auf sehr effiziente Weise einen Gleichheitsabgleich durchführen:

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

Mit der EXPLAIN-Ausgabe:

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

                                            }
                                    }
                            }
                    }
            },

Der Index wird also verwendet und kann angezeigt werden, um den Inhalt vorzufiltern, der dem Text entspricht, der durch die Ausgabe der anderen Bedingung gefunden wird.

Wenn Sie jedoch das "Präfix" zum Index als "Text"-Feld(er) zum Suchen behalten:

db.texty.dropIndexes();

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

Führen Sie dann die Suche durch:

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

Dann sehen Sie ein ähnliches Ergebnis wie bei der obigen "Gleichheitsübereinstimmung":

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

                                            }
                                    }
                            }
                    }
            },

Der große Unterschied hier zum ersten Versuch ist, wo filter wird in der Verarbeitungskette platziert, was darauf hinweist, dass der Inhalt zwar keine „Präfix“-Übereinstimmung ist (was am besten ist), aber tatsächlich „bevor“ er an die „Text“-Stufe gesendet wird, vom Index abgescannt wird.

Es ist also "vorgefiltert", aber natürlich nicht auf die optimalste Weise, und das liegt an der Art und Weise, wie der "Text" -Index verwendet wird. Wenn Sie also nur den einfachen Bereich eines Index selbst betrachtet haben:

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

Dann die EXPLAIN-Ausgabe:

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

Dann hat das wenigstens die indexBounds zu berücksichtigen und betrachtete nur den Teil des Index, der innerhalb dieser Grenzen lag.

Das sind also die Unterschiede hier. Die Verwendung einer "zusammengesetzten" Struktur sollte Ihnen hier einige Iterationszyklen ersparen, indem Sie die Auswahl einschränken können, aber sie muss immer noch alle Indexeinträge zum Filtern scannen und darf dies natürlich nicht das "Präfix"-Element im Index sein, es sei denn, Sie können eine Gleichheitsübereinstimmung darauf verwenden.

Ohne eine zusammengesetzte Struktur im Index geben Sie die Textergebnisse immer „zuerst“ zurück und wenden dann alle anderen Bedingungen auf diese Ergebnisse an. Außerdem ist es aufgrund der Handhabung der Abfrage-Engine nicht möglich, die Ergebnisse aus der Betrachtung eines „Text“-Index und eines „normalen“ Index zu „kombinieren/überschneiden“. Dies ist im Allgemeinen nicht der optimale Ansatz, daher ist es wichtig, Überlegungen einzuplanen.

Kurz gesagt, idealerweise mit einem „Gleichheits“-Übereinstimmungs-„Präfix“ verbinden, und wenn nicht, dann „nach“ der Textdefinition in den Index aufnehmen.