Le cas principal ici est qu'un résultat de recherche "texte" prend généralement le pas sur les autres conditions de filtre dans la requête, et en tant que tel, il devient nécessaire "d'abord" d'obtenir des résultats à partir du composant "texte", puis de "scanner" pour d'autres conditions dans le document.
Ce type de recherche peut être difficile à optimiser avec une "plage" ou tout type de condition de correspondance "d'inégalité" en conjonction avec les résultats de la recherche de texte, et est principalement dû à la façon dont MongoDB gère ce type d'index "spécial".
Pour une courte démonstration, considérez la configuration de base suivante :
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 })
Donc, si vous vouliez regarder cela avec une condition de recherche de texte ainsi qu'une considération de plage sur l'autre champ ( { "$lt": "c" }
), alors vous pouvez gérer comme suit :
db.texty.find({ "a": { "$lt": "c" }, "$text": { "$search": "something" } }).explain()
Avec la sortie d'explication telle que ( partie importante ):
"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" : {
}
}
}
}
}
},
Ce qui revient essentiellement à dire "d'abord obtenez-moi les résultats du texte, puis filtrez les résultats récupérés par l'autre condition" . Il est donc clair que seul l'index "texte" est utilisé ici, puis tous les résultats qu'il renvoie sont ensuite filtrés en examinant le contenu.
Ce n'est pas optimal pour deux raisons, étant donné qu'il est probable que les données soient mieux limitées par la condition "plage" plutôt que par les correspondances de la recherche de texte. Deuxièmement, même s'il existe un index sur les autres données, il n'est pas utilisé ici à des fins de comparaison. Au lieu de cela, le document entier est chargé pour chaque résultat et le filtre est testé.
Vous pourriez alors envisager un format d'index "composé" ici, et il semblerait initialement logique que si la "plage" est plus spécifique à la sélection, alors incluez-la comme ordre préfixé des clés indexées :
db.texty.dropIndexes();
db.texty.createIndex({ "a": 1, "text": "text" })
Mais il y a un hic ici, puisque lorsque vous essayez à nouveau d'exécuter la requête :
db.texty.find({ "a": { "$lt": "c" }, "$text": { "$search": "something" } })
Cela entraînerait une erreur :
Erreur :erreur :{"waitedMS" :NumberLong(0),"ok" :0,"errmsg" :"erreur de traitement de la requête :ns=test.textyTree :$and\n a $lt \"c\"\n TEXT :query=something, language=english, caseSensitive=0, diacriticSensitive=0, tag=NULL\nSort :{}\nProj :{}\n planner a renvoyé une erreur :impossible d'utiliser l'index de texte pour satisfaire la requête $text (si l'index de texte est composé, les prédicats d'égalité sont-ils donnés pour tous les champs de préfixe ?)","code" :2}
Donc, même si cela peut sembler "optimal", la façon dont MongoDB traite la requête (et vraiment la sélection d'index) pour l'index "texte" spécial, il n'est tout simplement pas possible que cette "exclusion" en dehors de la plage soit possible.
Vous pouvez cependant effectuer une correspondance "d'égalité" sur celui-ci de manière très efficace :
db.texty.find({ "a": "b", "$text": { "$search": "something" } }).explain()
Avec la sortie d'explication :
"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" : {
}
}
}
}
},
Ainsi, l'index est utilisé et il peut être affiché pour "pré-filtrer" le contenu fourni au texte correspondant par la sortie de l'autre condition.
Si effectivement vous conservez le "préfixe" à l'index comme champ(s) "texte" à rechercher cependant :
db.texty.dropIndexes();
db.texty.createIndex({ "text": "text", "a": 1 })
Effectuez ensuite la recherche :
db.texty.find({ "a": { "$lt": "c" }, "$text": { "$search": "something" } }).explain()
Ensuite, vous voyez un résultat similaire à la correspondance "égalité" ci-dessus :
"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" : {
}
}
}
}
},
La grande différence ici par rapport à la première tentative étant où filter
est placé dans la chaîne de traitement, indiquant que bien qu'il ne s'agisse pas d'une correspondance de "préfixe" (ce qui est le plus optimal), le contenu est en effet scanné hors de l'index "avant" d'être envoyé à l'étape "texte".
Il est donc "pré-filtré" mais pas bien sûr de la manière la plus optimale, et cela est dû à la nature même de l'utilisation de l'index "texte". Donc, si vous venez de considérer la plage simple sur un index en elle-même :
db.texty.createIndex({ "a": 1 })
db.texty.find({ "a": { "$lt": "c" } }).explain()
Ensuite, la sortie d'explication :
"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\")"
]
}
}
},
Ensuite, cela a au moins obtenu le indexBounds
à prendre en compte et n'a examiné que la partie de l'index qui se situait dans ces limites.
Voilà donc les différences ici. L'utilisation d'une structure "composée" devrait vous faire économiser quelques cycles d'itération ici en étant capable d'affiner la sélection, mais elle doit toujours analyser toutes les entrées d'index pour filtrer, et ne doit bien sûr pas être l'élément "préfixe" dans l'index, sauf si vous pouvez utiliser une correspondance d'égalité dessus.
Sans structure composée dans l'index, vous renvoyez toujours les résultats textuels "en premier", puis appliquez toutes les autres conditions à ces résultats. De plus, il n'est pas possible de "combiner/couper" les résultats en regardant un index "texte" et un index "normal" en raison de la gestion du moteur de requête. Ce n'est généralement pas l'approche optimale, il est donc important de planifier les considérations.
En bref, idéalement composé avec un "préfixe" de correspondance "d'égalité", et si ce n'est pas le cas, incluez dans l'index "après" la définition du texte.