在下面的代码中,我正在查询日期和按名称排序(这听起来很奇怪,我不索引日期字段,但我这样做是为了避免在内存中排序,这就是为什么我按名称索引)。如果我正在运行一个解释,我得到以下内容:
-> index on name
cursor: BtreeCursor name_1
scanAndOrder: False
nscanned: 1000
nscannedObjects: 1000
n:49
millis:1
然后,如果我创建一个包含名称和日期的复合索引,我得到以下输出:
-> index on name + date
cursor: BtreeCursor name_1_date_1
scanAndOrder: False
nscanned: 1000
nscannedObjects: 1000
n:49
millis:1
即使我的查询不包含索引或其前缀,在我看来,索引应该能够在第二种情况下直接从索引中读取日期字段,因此nscannedObject应该等于n = 49。事实上,所有信息都已经在索引中,扫描文档的数量应该等于返回结果的数量。这里的情况似乎并非如此。是我错了还是我做错了什么?
import pymongo
from pymongo import MongoClient
import datetime
import random
def printCursorExplain(e):
print 'cursor: ' + e['cursor']
print 'scanAndOrder: ' + str(e['scanAndOrder'])
print 'nscanned: ' + str(e['nscanned'])
print 'nscannedObjects: ' + str(e['nscannedObjects'])
print 'n:' + str(e['n'])
print 'millis:' + str(e['millis'])
print '---------------------------------------------------------------------------------n'
client = MongoClient()
db = client.DBQStackOverflow
name_list = ["Sylvain", "Tweety", "Toto", "Titi", "Sylvester"]
YEAR_LIST = [2014]
def generateRandomDate():
YYYY = YEAR_LIST[random.randint(0,len(YEAR_LIST)-1)]
MM = random.randint(1,12)
DD = random.randint(1,28)
date = datetime.datetime(YYYY, MM, DD)
return date
def insert():
for i in range(0, 1000):
start_date = generateRandomDate()
name = name_list[random.randint(0,len(name_list)-1)]
db.collection.insert( {"date": start_date, "name" :name})
insert()
YYYY = 2014
MM = 5
DD = 1
dateCIS = datetime.datetime(YYYY, MM, DD)
YYYY = 2014
MM = 5
DD = 12
dateCIE = datetime.datetime(YYYY, MM, DD)
queryDict = {"date" : {"$gte": dateCIS, "$lte": dateCIE}}
db.collection.create_index([("name", pymongo.ASCENDING)])
db.collection.create_index([("name", pymongo.ASCENDING),("date", pymongo.ASCENDING)], pymongo.ASCENDING)
print "-> index on name"
cursor1 = db.collection.find(queryDict).hint([("name", pymongo.ASCENDING)]).sort([("name", pymongo.ASCENDING)])#.limit(100)
e1 = cursor1.explain()
printCursorExplain(e1)
print "-> index on name + date"
cursor2 = db.collection.find(queryDict).hint([("name", pymongo.ASCENDING),("date", pymongo.ASCENDING)]).sort([("name", pymongo.ASCENDING)])#.limit(100)
e2 = cursor2.explain()
printCursorExplain(e2)
由于类似的原因,您的两个索引都导致对索引键(nscanned
)和文档(nscannedObjects
)进行完整扫描。
name索引
由于您正在按date
搜索并按name
排序,因此该索引可用于以正确的排序顺序返回结果。但是date
值需要与每个文档进行比较,以确定查询是否匹配。
名称+日期的索引
name
前缀仍然匹配您的排序顺序,但是{name, date}
上的复合索引不能有效地用于匹配date
值,因为必须首先检查所有name
值。这实际上与第一个索引的结果相同。
推荐指数
如果您在date
上查询并按name
排序,则最佳索引顺序实际上应该是{date, name}
。这将使索引对匹配date
值和按name
排序的返回结果都很有用。
注意:一般情况下,您不希望使用hint()
命令强制使用特定索引(尽管我假设您在本例中这样做是为了测试结果)。如果查询优化器没有选择您期望的索引,则很可能该索引不是最佳选择。
你应该会发现这篇博文很有帮助:优化MongoDB复合索引。
关键是MongoDB不能使用任何一个索引来确定哪些文档匹配查询条件。它可以使用任一索引来帮助排序。所以MongoDB扫描整个索引,因为这将以正确的顺序返回文档,但仍然需要拉出每个文档(nScannedObjects = 1000)来检查它是否匹配查询条件。