MongoDB: что это, зачем нужна и когда не нужна
✓ Все примеры выполнены на MongoDB 8.2.6, драйвер mongodb 7.5.0, Node.js v22.23.1 — июль 2026
Содержание
MongoDB — самая популярная документоориентированная база данных, и почти вся путаница вокруг неё возникает из-за одного вопроса: чем документ отличается от строки таблицы и что это меняет на практике.
Документ вместо строки
MongoDB хранит объекты целиком, а не разложенными по таблицам.
В реляционной базе пользователь с адресом и телефонами — это три таблицы и JOIN при каждом чтении. В MongoDB это один документ:
{
_id: ObjectId('6a59e5dd7368400e383caa62'),
name: 'Аня',
age: 30,
city: 'Москва',
теги: ['vip'],
адрес: { улица: 'Тверская', дом: 7 }
}
Вложенные объекты и массивы — обычные значения полей. Прочитать такого пользователя — одно обращение к базе: всё, что нужно, лежит в одном месте на диске.
Отсюда и вся выгода, и вся расплата. Выгода: чтение цельной сущности быстрое и простое. Расплата: если те же данные нужны в другом разрезе — например, «все пользователи с улицы Тверской», — придётся либо индексировать вложенное поле, либо дублировать данные.
Словарь для перехода с SQL:
| SQL | MongoDB |
|---|---|
| таблица | коллекция |
| строка | документ |
| колонка | поле |
| первичный ключ | _id |
| JOIN | вложенность или $lookup (дороже) |
| схема таблицы | отсутствует |
SQL: три таблицы
Пользователь разложен по таблицам, читается JOIN-ом
users— имя, возрастaddresses— улица, дом, FK на usersphones— номер, FK на users- Чтение профиля = JOIN трёх таблиц
- Схема жёсткая: новое поле = ALTER TABLE
MongoDB: один документ
Всё, что нужно, лежит рядом на диске
{ name, age, адрес: {…}, телефоны: […] }- Чтение профиля = одно обращение
- Вложенность — обычное значение поля
- Схемы нет: новое поле = просто пишем
- Цена: тот же набор в другом разрезе — дублирование
Схемы действительно нет
Это не маркетинг, а буквальное поведение. Положим в одну коллекцию документы разной формы:
await users.insertOne({ name: 'Аня', age: 30, city: 'Москва' });
await users.insertOne({ name: 'Егор', любимыйЦвет: 'синий', теги: ['a', 'b'] });
наборы полей у документов:
name, age, city
name, age
name, age, city
name, любимыйЦвет, теги
База не возразила. Ни один документ не «неправильный».
Что это даёт. Добавить поле — просто начать его писать. Никаких миграций, никакого ALTER TABLE на таблице в миллион строк, никакого простоя. Для данных, форма которых меняется или заранее неизвестна, это огромная экономия.
Чем это грозит. Схема никуда не делась — она просто переехала из базы в ваш код. Написали usre вместо user — документ запишется, а запрос по user его не найдёт. База не поможет: для неё оба поля одинаково законны.
Поэтому в реальных проектах схему возвращают: либо Mongoose со схемами и валидацией, либо $jsonSchema в валидаторе коллекции — то есть средствами самой MongoDB. Вопрос лишь в том, где вы её опишете.
Когда MongoDB выигрывает
- Форма данных разнородна. Каталог товаров, где у обуви размер, у ноутбука диагональ, а у книги ISBN. В SQL это либо десятки почти пустых колонок, либо таблица «атрибутов» с самодельным типизированием.
- Схема меняется часто. Продукт на ранней стадии: сегодня у сущности пять полей, через месяц восемь.
- Данные читаются целиком. Профиль, заказ, карточка — всё, что запрашивают одним куском.
- Естественная вложенность. Комментарии внутри поста, позиции внутри заказа.
- Горизонтальное масштабирование заложено изначально. Шардирование в MongoDB встроено, а не приделано сбоку.
Когда MongoDB проигрывает
Честный список важнее списка достоинств:
- Много связей. Если данные постоянно соединяются в разных комбинациях — это профиль реляционной базы.
$lookupв MongoDB есть, но он дороже JOIN и это признак того, что модель выбрана неверно. - Жёсткие требования к целостности. Финансы, учёт, всё, где недопустима запись «неправильной» формы. Внешние ключи и
NOT NULLв SQL делает сама база — здесь их придётся писать руками. - Сложная аналитика. Агрегации по многим сущностям — работа для SQL. Aggregation pipeline в MongoDB мощный, но многословный.
- Транзакции — норма, а не исключение. Они есть с версии 4.0 и с ACID-гарантиями, но обходятся дороже: модель предполагает, что связанные данные лежат в одном документе и транзакция не нужна.
Практическое правило: если вы уже нарисовали ER-диаграмму со связями — вам, скорее всего, нужен PostgreSQL. Если данные естественно ложатся в объект, который читают и пишут целиком, — MongoDB.
И честная оговорка про «MongoDB быстрее»: чаще всего скорость определяют индексы и модель данных, а не название базы. Запрос без индекса будет медленным в любой СУБД.
Индексы обязательны — так же, как в SQL
Распространённое заблуждение: раз базa «нереляционная», то и работает иначе. Не работает. Проверим на коллекции в 20 000 документов:
const plan = await big.find({ num: 19999 }).explain('executionStats');
БЕЗ индекса: этап = COLLSCAN | просмотрено документов = 20000 | мс = 7
С индексом : этап = FETCH | просмотрено документов = 1 | мс = 0
COLLSCAN — полный перебор коллекции. Ровно как Seq Scan в PostgreSQL. Подробнее — в разборе CRUD-операций.
Главное решение — не «какая база», а как разложить данные
В MongoDB нет JOIN, поэтому связь между сущностями вы решаете на этапе проектирования, и переиграть потом дорого. Развилок ровно две.
Вложить (embed) — положить связанные данные внутрь документа:
{ _id: 1, title: 'Пост', комментарии: [ { автор: 'Аня', текст: '…' } ] }
Читается одним запросом, всё рядом. Но: документ ограничен 16 МБ, а тысяча комментариев поедет к клиенту вместе с постом, даже если нужен был только заголовок.
Сослаться (reference) — хранить id и собирать вторым запросом:
{ _id: 1, title: 'Пост' }
{ _id: 99, postId: 1, автор: 'Аня', текст: '…' }
Документы остаются маленькими, комментариев может быть сколько угодно. Цена — два запроса или $lookup.
Правило, которое работает: вкладывайте, если данные читаются вместе, их немного и они принадлежат родителю (адрес пользователя, позиции заказа). Ссылайтесь, если данных может стать много, они нужны отдельно или живут своей жизнью.
Ошибка новичка — вкладывать всё подряд, потому что «так же удобно». Через год документ на 12 МБ, и каждое чтение профиля тянет всю историю активности.
Что было на этой странице в 2017 году
Первая версия вышла 13 августа 2017 года, когда актуальной была MongoDB 3.x. Основное объяснение — документы вместо таблиц, отсутствие схемы — не устарело: модель данных с тех пор не менялась.
Устарел контекст спора. В 2017-м вокруг MongoDB ещё висела репутация «базы, которая теряет данные» — наследие ранних версий, где запись по умолчанию не дожидалась подтверждения сервера. К современным версиям эта критика не относится: writeConcern по умолчанию требует подтверждения записи.
И главное: в 2017-м у MongoDB не было многодокументных транзакций — они появились в версии 4.0, в 2018-м. Это был сильнейший аргумент против неё, и статья того времени честно писала о нём как о данности. Сегодня аргумент снят: транзакции есть. Но выбор между MongoDB и PostgreSQL от этого не стал проще — он просто перестал быть спором о возможностях и стал вопросом модели данных.
Смежные темы: CRUD-операции MongoDB — весь синтаксис с замерами; вставка документов; промисы — почему драйвер асинхронный. Полный список — в справочнике по Node.js.
Частые вопросы
MongoDB быстрее PostgreSQL?
В MongoDB есть транзакции?
Правда, что MongoDB теряет данные?
writeConcern по умолчанию требует подтверждения, и та критика к текущим версиям не относится.