Основният случай тук е, че резултатът от търсенето на „текст“ обикновено има предимство пред другите условия на филтриране в заявката и като такъв става необходимо „първо“ да се получат резултати от компонента „текст“ и след това основно „сканиране“ за други условия в документа.
Този тип търсене може да бъде труден за оптимизиране заедно с „обхват“ или какъвто и да е тип условие за съответствие „неравенство“ във връзка с резултатите от текстовото търсене и се дължи най-вече на това как MongoDB обработва този „специален“ тип индекс.
За кратка демонстрация помислете за следната основна настройка:
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 })
Така че, ако искате да разгледате това с условие за текстово търсене, както и разглеждане на диапазон в другото поле ( { "$lt": "c" }
), тогава можете да се справите по следния начин:
db.texty.find({ "a": { "$lt": "c" }, "$text": { "$search": "something" } }).explain()
С изхода за обяснение като ( важна част ):
"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" : {
}
}
}
}
}
},
Което по същество означава „първи вземете ми текстовите резултати и след това филтрирайте тези резултати, извлечени от другото условие" . Така че ясно е, че тук се използва само индексът "текст", а след това всички резултати, които връща, впоследствие се филтрират чрез изследване на съдържанието.
Това не е оптимално поради две причини, тъй като може да се окаже, че данните са най-добре ограничени от условието "обхват", а не от съвпаденията от текстовото търсене. Второ, въпреки че има индекс за другите данни, той не се използва тук за сравнение. Така че по-скоро целият документ се зарежда за всеки резултат и филтърът се тества.
След това можете да помислите за "съставен" индексен формат тук и първоначално ще изглежда логично, че ако "диапазонът" е по-специфичен за избора, тогава го включете като префиксния ред на индексираните ключове:
db.texty.dropIndexes();
db.texty.createIndex({ "a": 1, "text": "text" })
Но тук има уловка, тъй като когато се опитате да изпълните заявката отново:
db.texty.find({ "a": { "$lt": "c" }, "$text": { "$search": "something" } })
Това би довело до грешка:
Грешка:грешка:{"waitedMS" :NumberLong(0),"ok" :0,"errmsg" :"грешка при обработка на заявката:ns=test.textyTree:$and\n a $lt \"c\"\n ТЕКСТ:query=something, language=english, caseSensitive=0, diacriticSensitive=0, tag=NULL\nСортиране:{}\nProj:{}\n планерът върна грешка:неуспешно използване на текстов индекс за удовлетворяване на заявка $text (ако текстовият индекс е съединение, дадени ли са предикатите за равенство за всички префиксни полета?)","code" :2}
Така че, въпреки че това може да изглежда „оптимално“, начинът, по който MongoDB обработва заявката (и наистина селекцията на индекс) за специалния „текст“ индекс, просто не е възможно това „изключване“ извън диапазона да е възможно.
Можете обаче да извършите съвпадение за "равенство" по много ефективен начин:
db.texty.find({ "a": "b", "$text": { "$search": "something" } }).explain()
С обяснения изход:
"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" : {
}
}
}
}
},
Така че индексът се използва и може да се покаже, че „предварително филтрира“ съдържанието, предоставено на текста, съвпадащ от изхода на другото условие.
Ако наистина запазите „префикса“ към индекса като поле(а) „текст“ за търсене обаче:
db.texty.dropIndexes();
db.texty.createIndex({ "text": "text", "a": 1 })
След това извършете търсенето:
db.texty.find({ "a": { "$lt": "c" }, "$text": { "$search": "something" } }).explain()
След това виждате резултат, подобен на горното съвпадение за "равенство":
"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" : {
}
}
}
}
},
Голямата разлика тук от първия опит е къде filter
се поставя във веригата за обработка, което показва, че макар да не съответства на "префикс" (което е най-оптималното), съдържанието наистина се сканира от индекса "преди" да бъде изпратено на етап "текст".
Така че той е "предварително филтриран", но не разбира се по най-оптималния начин и това се дължи на самото естество на това как се използва индексът "текст". Така че, ако току-що разгледате обикновения диапазон на индекс сам по себе си:
db.texty.createIndex({ "a": 1 })
db.texty.find({ "a": { "$lt": "c" } }).explain()
След това обяснява изходът:
"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\")"
]
}
}
},
Тогава поне получи indexBounds
да разгледа и разгледа само тази част от индекса, която попада в тези граници.
Така че това са разликите тук. Използването на "сложна" структура би трябвало да ви спести някои итерационни цикли тук, като сте в състояние да стесните избора, но все пак трябва да сканира всички записи в индекса, за да филтрира, и разбира се не бъде елементът "prefix" в индекса, освен ако не можете да използвате съвпадение за равенство върху него.
Без съставна структура в индекса, вие винаги връщате текстовите резултати "първо", а след това прилагате всякакви други условия към тези резултати. Също така не е възможно да се "комбинират/пресичат" резултатите от разглеждането на "текстов" индекс и "нормален" индекс поради обработката на машината за заявки. Това обикновено няма да е оптималният подход, така че планирането с оглед на съображенията е важно.
Накратко, в идеалния случай съставен с „равенство“ съвпада с „префикс“ и ако не, тогава включете в индекса „след“ текстовата дефиниция.