Раздел 36 · MongoDB с нуля

explain: как понять, что делает запрос

junior~20 мин

без входаВойди, чтобы отмечать прогресс, вести заметки и решать задачи в редакторе. войти

В прошлом уроке мы создавали индексы, но поверили на слово, что они помогают. Сегодня проверим. У MongoDB можно спросить, как она выполнила запрос: перебирала ли всю коллекцию, пользовалась ли индексом, сколько документов прочитала ради ответа. Отвечает на это explain. Это последний урок кластера про агрегацию и индексы.

Навигатор и трек поездки

Навигатор перед поездкой показывает маршрут: «по проспекту, потом налево по мосту». А после поездки фитнес-браслет показывает трек: где ты ехал, сколько стоял в пробке, сколько километров намотал.

explain умеет и то, и другое:

  • explain() без аргумента показывает план: каким путём сервер собирается идти.
  • explain('executionStats') выполняет запрос и показывает трек: сколько документов и ключей индекса пришлось прочитать.

В PostgreSQL это пара EXPLAIN и EXPLAIN ANALYZE, мы разбирали её в уроке про чтение планов.

Нужна большая коллекция

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

use shopstats
const docs = []
for (let i = 0; i < 100000; i++) {
  docs.push({ _id: i, book_id: (i % 9) + 1, user: 'u' + (i % 5000), day: i % 30 })
}
db.views.insertMany(docs)

Ответ insertMany получится длинным, сто тысяч номеров, его можно пролистать. У каждого пользователя ровно двадцать просмотров.

Запрос без индекса

Найдём просмотры одного пользователя и спросим, как сервер это сделал:

db.views.find({ user: 'u42' }).explain('executionStats')

Ответ большой, больше полусотни строк. Вот главные части, остальное заменено многоточием:

{
  explainVersion: '1',
  queryPlanner: {
    namespace: 'shopstats.views',
    parsedQuery: { user: { '$eq': 'u42' } },
    ...
    winningPlan: {
      isCached: false,
      stage: 'COLLSCAN',
      filter: { user: { '$eq': 'u42' } },
      nss: 'shopstats.views',
      direction: 'forward'
    },
    rejectedPlans: []
  },
  executionStats: {
    executionSuccess: true,
    nReturned: 20,
    executionTimeMillis: 49,
    totalKeysExamined: 0,
    totalDocsExamined: 100000,
    ...
  },
  ...
  ok: 1
}

Читаем сверху вниз.

winningPlan это выбранный план. Его поле stage называет главный этап. COLLSCAN значит полный просмотр коллекции: сервер прочитал документы подряд и каждый проверил на user: 'u42'.

executionStats это трек поездки:

ПолеЗначениеСмысл
nReturned20сколько документов вернул запрос
totalKeysExamined0сколько записей индекса прочитано
totalDocsExamined100000сколько документов прочитано
executionTimeMillis49сколько миллисекунд шёл запрос

Сто тысяч прочитанных документов ради двадцати нужных. Это главный сигнал: когда totalDocsExamined во много раз больше nReturned, запросу не хватает индекса. Время у тебя будет другим, оно зависит от машины. Счётчики документов и ключей от машины не зависят, поэтому смотреть в первую очередь стоит на них.

урок из курса

Урок из курса «MongoDB с нуля». Курс покупается один раз и остаётся навсегда: 500 ₽.

Оплата пока не подключена: премиум и курсы открываются промокодом.