sql >> Base de Datos >  >> NoSQL >> MongoDB

Combinar texto completo con otro índice

El caso principal aquí es que un resultado de búsqueda de "texto" generalmente tiene prioridad sobre otras condiciones de filtro en la consulta y, como tal, se vuelve necesario "primero" obtener resultados del componente "texto", y luego básicamente "escanear" para otras condiciones en el documento.

Este tipo de búsqueda puede ser difícil de optimizar junto con un "rango" o cualquier tipo de condición de coincidencia de "desigualdad" junto con los resultados de búsqueda de texto, y se debe principalmente a cómo MongoDB maneja este tipo de índice "especial".

Para una breve demostración, considere la siguiente configuración básica:

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

Entonces, si quisiera ver esto con una condición de búsqueda de texto, así como una consideración de rango en el otro campo ( { "$lt": "c" } ), entonces podría manejarlo de la siguiente manera:

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

Con la salida explicada como (parte 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" : {

                                                    }
                                            }
                                    }
                            }
                    }
            },

Que básicamente es decir "primero consígame los resultados de texto y luego filtre los resultados obtenidos por la otra condición" . Claramente, aquí solo se usa el índice de "texto" y luego todos los resultados que devuelve se filtran posteriormente al examinar el contenido.

Esto no es óptimo por dos razones, ya que es probable que los datos estén mejor restringidos por la condición de "rango" en lugar de las coincidencias de la búsqueda de texto. En segundo lugar, a pesar de que hay un índice sobre los otros datos, no se utiliza aquí para comparar. Así que se carga todo el documento para cada resultado y se prueba el filtro.

Entonces podría considerar un formato de índice "compuesto" aquí, y inicialmente parecería lógico que si el "rango" es más específico para la selección, entonces incluya eso como el orden prefijado de las claves indexadas:

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

Pero hay un problema aquí, ya que cuando intenta ejecutar la consulta nuevamente:

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

Daría como resultado un error:

Error:error:{"waitedMS" :NumberLong(0),"ok" :0,"errmsg" :"error al procesar la consulta:ns=test.textyTree:$and\n a $lt \"c\"\n TEXT :consulta=algo, idioma=inglés, caseSensitive=0, diacriticSensitive=0, tag=NULL\nOrdenar:{}\nProj:{}\n el planificador devolvió un error:no se pudo usar el índice de texto para satisfacer la consulta de $texto (si el índice de texto es compuesto, ¿se dan predicados de igualdad para todos los campos de prefijo?)","code" :2}

Entonces, aunque eso puede parecer "óptimo", la forma en que MongoDB procesa la consulta (y realmente la selección del índice) para el índice especial de "texto", simplemente no es posible que esta "exclusión" fuera del rango sea posible.

Sin embargo, puede realizar una coincidencia de "igualdad" en esto de una manera muy eficiente:

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

Con la salida de explicación:

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

                                            }
                                    }
                            }
                    }
            },

Por lo tanto, se usa el índice y se puede mostrar que "filtra previamente" el contenido proporcionado al texto que coincide con la salida de la otra condición.

Sin embargo, si mantiene el "prefijo" en el índice como campo (s) de "texto" para buscar:

db.texty.dropIndexes();

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

Luego realice la búsqueda:

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

Luego verá un resultado similar a la coincidencia de "igualdad" anterior:

            "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 gran diferencia aquí desde el primer intento es donde filter se coloca en la cadena de procesamiento, lo que indica que, si bien no es una coincidencia de "prefijo" (que es lo más óptimo), el contenido se está escaneando del índice "antes" de enviarlo a la etapa de "texto".

Por lo tanto, está "prefiltrado" pero, por supuesto, no de la manera más óptima, y ​​esto se debe a la naturaleza misma de cómo se usa el índice de "texto". Entonces, si solo consideró el rango simple en un índice por sí mismo:

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

Luego la salida de explicación:

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

Entonces eso al menos obtuvo el indexBounds a considerar y solo analizó la parte del índice que se encontraba dentro de esos límites.

Esas son las diferencias aquí. El uso de una estructura "compuesta" debería ahorrarle algunos ciclos de iteración aquí al poder reducir la selección, pero aún debe escanear todas las entradas del índice para filtrar y, por supuesto, no ser el elemento "prefijo" en el índice a menos que pueda usar una coincidencia de igualdad en él.

Sin una estructura compuesta en el índice, siempre devuelve los resultados de texto "primero" y luego aplica cualquier otra condición a esos resultados. Además, no es posible "combinar/intersectar" los resultados de mirar un índice de "texto" y un índice "normal" debido al manejo del motor de consultas. Por lo general, ese no será el enfoque óptimo, por lo que es importante planificar las consideraciones.

En resumen, idealmente compuesto con un "prefijo" de coincidencia de "igualdad", y si no, incluir en el índice "después" de la definición del texto.