Пять лучших практик написания высокоэффективных запросов MongoDB
Улучшите производительность запросов MongoDB с помощью лучших индексов, проекций, избегания сканирования, планирования сортировки и целевых обновлений.
Пять лучших практик написания высокоэффективных запросов MongoDB
Запросы MongoDB могут казаться быстрыми во время разработки, но сильно замедляться по мере роста коллекций. Высокоэффективные запросы MongoDB зависят от соответствия ваших индексов реальным шаблонам доступа, возврата только полезных полей и избегания операций, которые вынуждают выполнять большие сканирования.
Эти пять практик помогут вам поддерживать предсказуемость чтения и уменьшить ненужную работу на сервере.
1. Стратегическое индексирование для поддержки ваших запросов
Самый важный фактор производительности запросов — наличие и правильное использование индексов. Индекс позволяет планировщику запросов быстро находить соответствующие документы без необходимости сканировать каждый документ в коллекции ("COLLSCAN").
Как работает индексирование
MongoDB использует индексы для удовлетворения предикатов запроса (части filter вашего запроса). Если запрос использует поля, которые являются частью индекса, MongoDB может использовать этот индекс для быстрого сужения результирующего набора.
Лучшая практика: Всегда анализируйте свои распространенные шаблоны запросов. Если вы часто запрашиваете или сортируете по полям A, B и C, рассмотрите возможность создания составного индекса на { A: 1, B: 1, C: 1 }.
Избегание неиндексированных сканирований
Если запрос не может использовать индекс, MongoDB по умолчанию выполняет сканирование коллекции (COLLSCAN), которое читает каждый документ в коллекции. Это крайне медленно на больших наборах данных.
Совет: Используйте метод explain('executionStats') для вашего запроса, чтобы проверить winningPlan и totalKeysExamined по сравнению с totalDocsExamined. Большое расхождение часто указывает на плохое использование индекса или отсутствие индекса.
// Пример: Проверка производительности запроса
db.users.find({ status: "active" }).explain('executionStats')
2. Используйте проекцию для ограничения возвращаемых полей
При выполнении запроса MongoDB по умолчанию возвращает весь соответствующий документ. Во многих приложениях вам нужны только несколько полей (например, отображение списка имен). Извлечение ненужных больших полей (таких как встроенные массивы или большие текстовые блоки) увеличивает задержку сети, использование памяти на сервере базы данных и потребление памяти клиентом.
Проекция позволяет указать, какие именно поля должны быть возвращены.
Синтаксис проекции
Используйте второй аргумент в методе find(), чтобы указать поля для включения (1) или исключения (0).
_idвключается по умолчанию, если явно не исключен (_id: 0).
// Неэффективно: Возвращает весь документ пользователя
db.users.find({ organizationId: "XYZ" })
// Эффективно: Возвращает только имя и email пользователя
db.users.find(
{ organizationId: "XYZ" },
{ name: 1, email: 1, _id: 0 } // Включить name и email, исключить _id
)
Предупреждение: Проекция лучше всего работает в сочетании с индексированными полями. Если запрос все еще требует полного сканирования, проекция полей экономит только пропускную способность сети, но не улучшает время начального поиска.
3. Избегайте операций, которые вынуждают полное сканирование коллекции
Некоторые операции запросов по своей сути трудно или невозможно удовлетворить с помощью стандартных индексов MongoDB, что часто приводит к дорогостоящим полным сканированиям коллекции, даже если индексы существуют.
Избегайте ведущих подстановочных знаков в регулярных выражениях
Индексы структурированы иерархически (как книжный указатель, организованный в алфавитном порядке). Регулярное выражение, начинающееся с подстановочного знака (.*), не может использовать индекс, поскольку начальная точка поискового термина неизвестна.
- Обычно дружественно к индексу:
db.products.find({ sku: /^ABC/ }) - Обычно дорого:
db.products.find({ sku: /.*CDE$/ })
Совет: Если вам нужно искать внутри строковых значений, рассмотрите возможность использования текстовых индексов MongoDB для возможностей полнотекстового поиска или нормализуйте структуру данных для поддержки префиксного поиска.
Будьте осторожны с запросами к неиндексированным полям
Как упоминалось ранее, запросы к полям, которые не индексированы, вынуждают сканирование. Будьте особенно осторожны со сложными запросами, включающими предложения $where или оценку функций JavaScript, так как они почти всегда приводят к сканированию каждого документа.
4. Оптимизируйте операции сортировки (покрытые запросы)
Сортировка результатов с помощью метода .sort() требует от MongoDB либо извлечения всех соответствующих документов и их сортировки в памяти (если набор мал), либо использования плана выполнения с сортировкой по индексу (если индекс поддерживает порядок сортировки).
Если MongoDB не может использовать индекс для сортировки, может потребоваться блокирующая сортировка в памяти, и она может завершиться ошибкой, когда сортировка превышает лимит памяти сервера для блокирующих операций сортировки.
Лучшая практика: Используйте покрытые запросы для сортировки
Покрытый запрос — это запрос, в котором все поля, участвующие в предикате запроса, проекции и операции сортировки, содержатся в одном индексе. Когда запрос покрыт, MongoDB никогда не приходится смотреть на фактические документы — он получает все необходимое непосредственно из структуры индекса.
// Предположим индекс: { category: 1, price: -1 }
// Эффективный покрытый запрос:
db.inventory.find(
{ category: "Electronics" }, // Поле запроса в индексе
{ price: 1, _id: 0 } // Поле проекции в индексе
).sort({ price: -1 }) // Поле сортировки в индексе
5. Предпочитайте атомарные обновления и операции записи
Хотя эта статья сосредоточена на производительности чтения, эффективные записи значительно способствуют общему здоровью базы данных, уменьшая блокировки и конкуренцию. Обновления должны быть максимально целевыми.
Используйте операторы обновления вместо замены целых документов
При изменении документа используйте конкретные операторы обновления, такие как $set, $inc или $push, вместо чтения документа, его изменения на стороне клиента и записи всего документа обратно.
Неэффективно: Чтение всего документа -> Изменение в приложении -> Запись всего документа обратно.
Эффективно: Используйте атомарные операторы для изменения только необходимых полей.
// Эффективное обновление: Атомарно увеличивает счетчик, не затрагивая другие поля
db.metrics.updateOne(
{ metricName: "login_attempts" },
{ $inc: { count: 1 } }
)
Используя атомарные операторы, вы минимизируете вероятность конфликтов записи и уменьшаете объем данных, передаваемых по сети.
Ключевой вывод
Написание высокоэффективных запросов MongoDB вращается вокруг сотрудничества между логикой вашего приложения и использованием индексов движком базы данных. Следуя этим пяти лучшим практикам, вы можете гарантировать, что ваши чтения будут быстрыми, масштабируемыми и ресурсоэффективными:
- Индексируйте стратегически: Убедитесь, что индексы существуют для ваших распространенных фильтров запросов и критериев сортировки.
- Используйте проекцию: Извлекайте только те поля, которые вам абсолютно необходимы.
- Избегайте сканирований: Избегайте ведущих подстановочных знаков в регулярных выражениях и предложений
$where. - Оптимизируйте сортировку: Стремитесь к покрытым запросам, где индекс содержит все необходимые поля для запроса, проекции и сортировки.
- Предпочитайте атомарные записи: Используйте операторы, такие как
$set, чтобы минимизировать накладные расходы при обновлениях.
Регулярно просматривайте журналы медленных запросов и используйте explain(), чтобы убедиться, что ваши запросы используют созданные вами индексы. Настройка производительности — это непрерывный процесс, но эти практики формируют прочную основу для высокопроизводительного развертывания MongoDB.