Прямой ответ
Документная база хранит записи как самодостаточные документы — обычно в формате BSON (бинарное представление JSON). Каждый документ содержит пары «поле — значение», где значением может быть вложенный документ или массив. Схема не задаётся заранее для всей коллекции: разные документы в одной коллекции могут иметь разный набор полей.
Ключевые понятия MongoDB:
- Документ — аналог строки, но с вложенностью. Ограничение размера — 16 МБ.
- Коллекция — аналог таблицы без фиксированной схемы.
_id— обязательный первичный ключ; если не задан, генерируется как ObjectId, содержащий метку времени.- Индексы — по умолчанию только по
_id; остальные создаются явно, включая составные и по вложенным полям.
Главное решение при проектировании — встраивать или ссылаться:
| Подход | Когда применять | Риск |
|---|---|---|
| Встраивание (embed) | Данные читаются вместе, отношение «один к немногим», подчинённые не нужны отдельно | Разрастание документа, дублирование |
| Ссылка (reference) | Данные нужны отдельно, отношение «ко многим», подчинённые меняются независимо | Несколько запросов, $lookup дорог |
// встраивание: заказ и его позиции читаются одним запросом
{
_id: ObjectId("..."),
number: "A-1042",
customer: { id: 42, name: "Иванов" }, // денормализовано для чтения
items: [
{ sku: "KB-01", qty: 1, price: 2500 },
{ sku: "MS-07", qty: 2, price: 900 }
],
total: 4300,
status: "paid"
}
Типичная ошибка — встраивание неограниченно растущего массива: история событий, комментарии, логи. Документ упирается в лимит 16 МБ, а каждое обновление переписывает его целиком. Правило: встраивать то, что имеет естественную границу.
Важно понимать, что гибкая схема не отменяет схему. Она переносит контроль из базы в приложение: валидация, миграции и согласованность полей становятся задачей разработчика.
Практика: проектирование и эксплуатация
1. Проектируйте от запросов, а не от сущностей. Документная модель строится вокруг того, какие данные нужны в одном чтении. Одна и та же предметная область даёт разные схемы для разных сценариев использования.
2. Индексы обязательны. Запрос без индекса выполняет перебор коллекции (COLLSCAN). Проверка плана — через explain():
db.orders.find({ "customer.id": 42, status: "paid" }).explain("executionStats");
// stage: "COLLSCAN" — индекса нет, нужен составной индекс
db.orders.createIndex({ "customer.id": 1, status: 1 });
Порядок полей в составном индексе имеет значение: сначала поля, по которым filtering равенством, затем диапазонные. О принципах — в статье про индексы.
3. Транзакции существуют, но не бесплатны. Начиная с версии 4.0 MongoDB поддерживает многодокументные транзакции на replica set, с 4.2 — в шардированных кластерах. Они дают привычные гарантии, но дороже одиночных операций и требуют аккуратности с длительностью.
4. Агрегации — основной инструмент отчётности. Конвейер агрегации ($match, $group, $lookup, $project) заменяет соединения и группировки. $lookup — аналог JOIN, но работает заметно дороже, чем соединение в реляционной СУБД.
5. Обновление вложенного массива — частая боль. Операторы $push, $pull, $set с позиционными фильтрами решают задачу, но делают код менее очевидным, чем обычный SQL.
6. JSON есть и в реляционных СУБД. PostgreSQL поддерживает тип jsonb с индексами по вложенным полям. Если основной объём данных реляционный, а гибкая схема нужна для нескольких сущностей, отдельная документная база может быть избыточна.
Рекомендации:
- Не храните в документе то, что растёт без границы. События, комментарии, вложения — в отдельные коллекции со ссылкой.
- Денормализуйте осознанно. Скопированное имя клиента в заказе удобно для чтения, но требует обновления при изменении. Решите заранее, допустима ли устаревшая копия.
- Введите контроль схемы в приложении. Валидация на уровне схемы или модели предотвращает появление документов с недостающими полями.
- Планируйте миграции. Изменение структуры существующих документов — это скрипт обновления по всей коллекции, а не одна команда.
- Следите за размером документов. Рост среднего размера — признак того, что встраивание зашло слишком далеко.
Практический сценарий: документ с историей событий перестал обновляться
Контекст: карточка клиента содержала встроенный массив events. Через год работы часть записей перестала принимать новые события, а чтение карточки занимало секунды.
- Диагностика. Средний размер документа вырос до 4,2 МБ, у активных клиентов — до 14 МБ. Каждое обновление переписывало документ целиком, что приводило к высокой нагрузке на запись.
- Причина. История событий не имела естественной границы: у активных клиентов накопилось десятки тысяч записей. При достижении лимита запись начинала отклоняться.
- Действия. (а) События вынесли в отдельную коллекцию
customer_eventsс индексом по клиенту и времени; (б) в карточке оставили только счётчики и последнее событие; (в) написали скрипт переноса с обработкой пачками и проверкой контрольных сумм; (г) добавили метрику на размер документа с порогом. - Результат. Средний размер карточки вернулся к 6 КБ, время обновления снизилось с 300 мс до 8 мс, чтение истории стало постраничным.
- Профилактика. В правила код-ревью добавили вопрос: «может ли этот массив расти неограниченно».
Вывод: гибкость документной модели требует дисциплины там, где реляционная база наложила бы ограничения автоматически. Большинство проблем документных схем — это последствия встраивания без учёта роста данных.
FAQ
Подходит ли MongoDB, если схема постоянно меняется? Частично. Гибкая схема помогает на этапе исследования, когда структура ещё не определена. Но в зрелом продукте структура стабилизируется, и отсутствие схемы в базе превращается в дополнительную работу по валидации и миграциям.
Чем документная база отличается от реляционной с JSON-колонкой?
Документная база строит вокруг документов индексы, агрегации и репликацию. Реляционная СУБД с jsonb даёт ту же вложенность, но в рамках транзакционной модели и знакомых инструментов. Выбор зависит от того, что преобладает в данных.
Нужны ли транзакции в документной базе? Если операция затрагивает один документ, транзакция не нужна — обновления отдельного документа атомарны. Многодокументные транзакции требуются там, где согласованность между записями критична.
Что делать с отчётностью по документным данным? Конвейер агрегации закрывает большинство задач. Для тяжёлой аналитики данные обычно выгружают в хранилище — о различии нагрузок в статье про OLTP и OLAP.
Источники: MongoDB docs (data modeling, transactions), PostgreSQL docs (JSON types) — ссылки в шапке.