Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Mongodb query does not use prefix on compound index with text field

I've created the following index on my collection:

db.myCollection.createIndex({
  user_id: 1,
  name: 'text'
})

If I try to see the execution plan of a query containing both fields, like this:

db.getCollection('campaigns').find({ 
    user_id: ObjectId('xxx')
   ,$text: { $search: 'bla' } 
}).explain('executionStats')

I get the following results:

...
"winningPlan" : {
    "stage" : "TEXT",
    "indexPrefix" : {
        "user_id" : ObjectId("xxx")
    },
    "indexName" : "user_id_1_name_text",
    "parsedTextQuery" : {
        "terms" : [ 
            "e"
        ],
        "negatedTerms" : [],
        "phrases" : [],
        "negatedPhrases" : []
    },
    "inputStage" : {
        "stage" : "TEXT_MATCH",
        "inputStage" : {
            "stage" : "TEXT_OR",
            "inputStage" : {
                "stage" : "IXSCAN",
                "keyPattern" : {
                    "user_id" : 1.0,
                    "_fts" : "text",
                    "_ftsx" : 1
                },
                "indexName" : "user_id_1_name_text",
                "isMultiKey" : true,
                "isUnique" : false,
                "isSparse" : false,
                "isPartial" : false,
                "indexVersion" : 1,
                "direction" : "backward",
                "indexBounds" : {}
            }
        }
    }
}
...

As stated in the documentation, MongoDB can use index prefixes to perform indexed queries.

Since user_id is a prefix for the index above, I'd expect that a query only by user_id would use the index, but if I try the following:

db.myCollection.find({ 
    user_id: ObjectId('xxx')
}).explain('executionStats')

I get:

...
"winningPlan" : {
    "stage" : "COLLSCAN",
    "filter" : {
        "user_id" : {
            "$eq" : ObjectId("xxx")
        }
    },
    "direction" : "forward"
},
...

So, it is not using the index at all and performing a full collection scan.

like image 803
Henrique Barcelos Avatar asked Jul 03 '17 20:07

Henrique Barcelos


People also ask

Is it possible to index text with MongoDB?

MongoDB provides text indexes to support text search queries on string content. text indexes can include any field whose value is a string or an array of string elements. A collection can only have one text search index, but that index can cover multiple fields.

Can MongoDB use part of a compound index?

MongoDB can use the intersection of indexes to fulfill queries. For queries that specify compound query conditions, if one index can fulfill a part of a query condition, and another index can fulfill another part of the query condition, then MongoDB can use the intersection of the two indexes to fulfill the query.

How does MongoDB compound index work?

Or in other words, compound indexes are those indexes where a single index field contains references to multiple fields. In MongoDB, the compound index can contain a single hashed index field, if a field contains more than one hashed index field then MongoDB will give an error.

Does MongoDB $in use index?

MongoDB uses indexing in order to make the query processing more efficient. If there is no indexing, then the MongoDB must scan every document in the collection and retrieve only those documents that match the query.


1 Answers

In general MongoDB can use index prefixes to support queries, however compound indexes including geospatial or text fields are a special case of sparse compound indexes. If a document does not include a value for any of the text index field(s) in a compound index, it will not be included in the index.

In order to ensure correct results for a prefix search, an alternative query plan will be chosen over the sparse compound index:

If a sparse index would result in an incomplete result set for queries and sort operations, MongoDB will not use that index unless a hint() explicitly specifies the index.

Setting up some test data in MongoDB 3.4.5 to demonstrate the potential problem:

db.myCollection.createIndex({ user_id:1, name: 'text' }, { name: 'myIndex'})

// `name` is a string; this document will be included in a text index
db.myCollection.insert({ user_id:123, name:'Banana' })

// `name` is a number; this document will NOT be included in a text index
db.myCollection.insert({ user_id:123, name: 456 })

// `name` is missing; this document will NOT be included in a text index
db.myCollection.insert({ user_id:123 })

Then, forcing the compound text index to be used:

db.myCollection.find({user_id:123}).hint('myIndex')

The result only includes the single document with the indexed text field name, rather than the three documents that would be expected:

{
  "_id": ObjectId("595ab19e799060aee88cb035"),
  "user_id": 123,
  "name": "Banana"
}

This exception should be more clearly highlighted in the MongoDB documentation; watch/upvote DOCS-10322 in the MongoDB issue tracker for updates.

like image 197
Stennie Avatar answered Oct 04 '22 19:10

Stennie