MongoDB
 sql >> Database >  >> NoSQL >> MongoDB

Combina il testo completo con un altro indice

Il caso principale qui è che un risultato di ricerca "testo" ha generalmente la precedenza su altre condizioni di filtro nella query, e come tale diventa necessario "prima" ottenere risultati dal componente "testo", quindi sostanzialmente "scansionare" per altre condizioni nel documento.

Questo tipo di ricerca può essere difficile da ottimizzare insieme a una condizione di corrispondenza "intervallo" o qualsiasi tipo di "disuguaglianza" in combinazione con i risultati della ricerca di testo, ed è principalmente dovuto al modo in cui MongoDB gestisce questo tipo di indice "speciale".

Per una breve dimostrazione, considera la seguente configurazione di base:

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

Quindi, se si desidera esaminare questo con una condizione di ricerca di testo e una considerazione sull'intervallo nell'altro campo ( { "$lt": "c" } ), quindi potresti gestire come segue:

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

Con l'output di spiegazione come (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" : {

                                                    }
                                            }
                                    }
                            }
                    }
            },

Che fondamentalmente sta dicendo "prima procurami i risultati di testo e poi filtra i risultati recuperati dall'altra condizione" . Quindi chiaramente qui viene utilizzato solo l'indice "testo" e quindi tutti i risultati che restituisce vengono successivamente filtrati esaminando il contenuto.

Questo non è ottimale per due ragioni, essendo che è probabile che i dati siano meglio vincolati dalla condizione "intervallo" piuttosto che dalle corrispondenze dalla ricerca di testo. In secondo luogo, anche se esiste un indice sugli altri dati, non viene utilizzato qui per il confronto. Quindi viene caricato l'intero documento per ogni risultato e il filtro viene testato.

Potresti quindi considerare un formato di indice "composto" qui, e sembrerebbe inizialmente logico che se l'"intervallo" è più specifico per la selezione, includilo come ordine prefissato delle chiavi indicizzate:

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

Ma c'è un problema qui, dal momento che quando tenti di eseguire nuovamente la query:

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

Risulterebbe in un errore:

Errore:errore:{"waitedMS" :NumberLong(0),"ok" :0,"errmsg" :"errore nell'elaborazione della query:ns=test.textyTree:$and\n a $lt \"c\"\n TESTO:query=qualcosa, language=english, caseSensitive=0, diacriticSensitive=0, tag=NULL\nOrdina:{}\nProj:{}\n Planner ha restituito un errore:impossibile utilizzare l'indice di testo per soddisfare la query $text (se l'indice di testo è composto, vengono forniti predicati di uguaglianza per tutti i campi di prefisso?)","code" :2}

Quindi, anche se può sembrare "ottimale", il modo in cui MongoDB elabora la query (e in realtà la selezione dell'indice) per lo speciale indice "testo", semplicemente non è possibile che questa "esclusione" al di fuori dell'intervallo sia possibile.

Tuttavia, puoi eseguire una corrispondenza di "uguaglianza" su questo in un modo molto efficiente:

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

Con l'output di spiegazione:

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

                                            }
                                    }
                            }
                    }
            },

Quindi l'indice viene utilizzato e può essere mostrato per "prefiltrare" il contenuto fornito al testo corrispondente dall'output dell'altra condizione.

Se invece mantieni il "prefisso" all'indice come campo/i di "testo" da cercare:

db.texty.dropIndexes();

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

Quindi esegui la ricerca:

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

Quindi vedrai un risultato simile alla corrispondenza "uguaglianza" sopra:

            "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 differenza qui dal primo tentativo è dove filter viene inserito nella catena di elaborazione, indicando che, sebbene non sia una corrispondenza "prefisso" (che è la più ottimale), il contenuto viene effettivamente scansionato dall'indice "prima" di essere inviato alla fase "testo".

Quindi è "prefiltrato" ma ovviamente non nel modo più ottimale, e ciò è dovuto alla natura stessa di come viene utilizzato l'indice "testo". Quindi, se hai appena considerato l'intervallo normale su un indice da solo:

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

Quindi l'output di spiegazione:

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

Quindi almeno quello ha ottenuto indexBounds considerare e guardare solo quella parte dell'indice che rientrava in quei limiti.

Quindi queste sono le differenze qui. L'uso di una struttura "composta" dovrebbe farti risparmiare alcuni cicli di iterazione qui potendo restringere la selezione, ma deve comunque scansionare tutte le voci dell'indice per filtrare e ovviamente non essere l'elemento "prefisso" nell'indice a meno che tu non possa utilizzare una corrispondenza di uguaglianza su di esso.

Senza una struttura composta nell'indice, restituisci sempre i risultati del testo "prima" e quindi applichi qualsiasi altra condizione a tali risultati. Inoltre, non è possibile "combinare/intersecare" i risultati osservando un indice "testo" e un indice "normale" a causa della gestione del motore di query. In genere non sarà l'approccio ottimale, quindi è importante pianificare le considerazioni.

In breve, idealmente composto con un "prefisso" di corrispondenza "uguaglianza" e, in caso contrario, includi nell'indice "dopo" la definizione del testo.