MongoDB: чтение индекса вместо чтения самих документов, уменьшение nscanned объектов

В приведенном ниже коде я запрашиваю дату и сортирую по имени (может показаться странным, что я не индексирую поле даты, но я делаю это, чтобы избежать сортировки в памяти, поэтому я индексирую по имени). Если я запускаю объяснение, я получаю следующее:

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

person scoulomb    schedule 18.07.2014    source источник


Ответы (2)


Оба ваших индекса приводят к полному сканированию ключей индекса (nscanned) и документов (nscannedObjects) по схожим причинам.

указатель на имя

Поскольку вы выполняете поиск по date и сортируете по name, этот индекс можно использовать для возврата результатов в правильном порядке сортировки... но значение date требует сравнения с каждым документом, чтобы определить, соответствует ли запрос.

индекс по имени + дата

Префикс name по-прежнему соответствует вашему порядку сортировки, но составной индекс для {name, date} нельзя эффективно использовать для сопоставления со значениями date, поскольку сначала необходимо проверить все значения name. Фактически это тот же результат, что и для первого индекса.

Рекомендуемый индекс

Если вы запрашиваете date и сортируете по name, оптимальный порядок индекса должен быть фактически {date, name}. Это сделает индекс полезным как для сопоставления значений date, и для возврата результатов, отсортированных по name.

Примечание: как правило, вы не хотите использовать команду hint() для принудительного использования определенного индекса (хотя я предполагаю, что в данном случае вы делаете это для проверки результатов). Если оптимизатор запросов не выбирает ожидаемый вами индекс, скорее всего, этот индекс — не лучший выбор.

Вам будет полезно прочитать эту запись в блоге: Оптимизация составных индексов MongoDB.

person Stennie    schedule 31.07.2014
comment
Привет, спасибо. На самом деле я проводил этот тест после прочтения упомянутой вами статьи :). - person scoulomb; 01.08.2014
comment
На самом деле автор говорит: Итак, я решил проблему scanAndOrder ценой более высокого nscanned. Я не могу уменьшить nscanned, но могу ли я уменьшить nscannedObjects? и ответ да. Действительно, nscanned нельзя уменьшить из-за ключевых порядков в index. Однако nscanned объект должен быть уменьшен, как объясняет автор. Действительно, зачем читать документ, если можно прочитать значение прямо из индекса? Но что звучит странно, так это то, что мой опыт не воспроизводит ожидаемое поведение... Значит, где-то есть что-то странное... - person scoulomb; 01.08.2014

Дело в том, что MongoDB не может использовать ни один из ваших индексов, чтобы определить, какие документы соответствуют критериям запроса. Он может использовать любой индекс, чтобы помочь с сортировкой. Таким образом, MongoDB сканирует весь индекс, потому что это вернет документы в правильном порядке, но все же необходимо получить каждый документ (nScannedObjects = 1000), чтобы проверить, соответствует ли он критериям запроса.

person wdberkeley    schedule 23.07.2014
comment
Спасибо, я согласен, что это не может уменьшить nscanned, но должно уменьшить nscqnnedObject (на мой взгляд), как я пытаюсь объяснить ниже. - person scoulomb; 01.08.2014