Гайды
Введение в MongoDB: CRUD, BSON и схемы данных
Коллекции и документы, insert/find/update, операторы фильтра, вложение vs ссылки, валидация JSON Schema и транзакции.
~14 мин чтения
Введение в MongoDB: CRUD, BSON и схемы данных
MongoDB — документоориентированная СУБД: данные хранятся как BSON-документы (бинарный JSON с типами Date, ObjectId, Decimal128 и т.д.) в коллекциях. Один документ может иметь другой набор полей, чем соседний — гибкость ценой дисциплины на уровне приложения и схемы.
Этот гайд закрывает базу: подключение, CRUD, фильтры и выбор модели вложение vs ссылки. Индексы и aggregation — в «Индексы и агрегации в MongoDB»; репликация и шарды — в «Репликация и шардирование MongoDB». Для сравнения с реляционным JSON см. JSONB в PostgreSQL.
1. Иерархия и термины
| Уровень | Что это |
|---|---|
| Кластер | В проде обычно replica set или sharded cluster |
| База данных | Контейнер коллекций и пользователей |
| Коллекция | Набор документов |
| Документ | BSON-объект (лимит 16 МБ на документ в классической модели) |
Поле _id уникально в коллекции. Если не задано, сервер или драйвер создаст ObjectId.
2. Установка и подключение
Официальная установка: документация MongoDB под вашу ОС или образ Docker mongo:7.
mongosh:
mongosh "mongodb://localhost:27017"
Connection string в приложениях:
mongodb://user:pass@host1:27017,host2:27017/mydb?replicaSet=rs0&authSource=admin
Параметры authSource, replicaSet, TLS проверяйте для production.
3. CRUD: создание
use shop
db.orders.insertOne({
customerId: "c-42",
items: [{ sku: "A1", qty: 2, price: 9.99 }],
status: "new",
createdAt: new Date()
})
db.orders.insertMany([
{ customerId: "c-1", status: "new", createdAt: new Date() },
{ customerId: "c-2", status: "paid", createdAt: new Date() }
], { ordered: false })
Для идемпотентности критичных ключей часто используют стабильный _id или уникальный индекс.
4. Чтение: find и проекция
db.orders.find(
{ customerId: "c-42", status: { $in: ["new", "paid"] } },
{ items: 1, status: 1, createdAt: 1, _id: 0 }
)
db.orders.findOne({ _id: ObjectId("65a1b2c3d4e5f678901234ab") })
db.orders.find({ status: "new" }).sort({ createdAt: -1 }).limit(20)
Частые операторы фильтра
| Оператор | Назначение |
|---|---|
$eq, $ne, $gt, $gte, $lt, $lte | Сравнения |
$in, $nin | Списки |
$and, $or, $not, $nor | Логика |
$exists | Наличие поля |
$regex | Строки (без индекса — COLLSCAN) |
$elemMatch | Условие на элемент массива |
db.orders.find({
items: { $elemMatch: { sku: "A1", qty: { $gte: 1 } } }
})
5. Обновление и замена
db.orders.updateOne(
{ _id: someId },
{
$set: { status: "shipped", updatedAt: new Date() },
$inc: { "items.$[elem].qty": 1 }
},
{ arrayFilters: [{ "elem.sku": "A1" }] }
)
db.orders.updateMany(
{ status: "new", createdAt: { $lt: new Date(Date.now() - 7*864e5) } },
{ $set: { status: "stale" } }
)
db.orders.replaceOne({ _id: someId }, { customerId: "c-9", status: "new" })
Массивы: $push, $pull, $addToSet, $[] / arrayFilters.
6. Удаление
db.orders.deleteOne({ _id: someId })
db.orders.deleteMany({ status: "cancelled", createdAt: { $lt: cutoff } })
Мягкое удаление: поле deletedAt вместо физического удаления.
7. Вложение vs ссылки
Встраивание (embedding)
Плюсы: один запрос, атомарные обновления документа. Минусы: размер документа, дублирование, сложные запросы «по всем вложенным сущностям».
Ссылки (referencing)
Плюсы: нормализация, отчёты. Минусы: несколько round-trip или $lookup, консистентность — приложение или транзакции.
Правила
- Вместе читается и меняется — чаще вложить.
- Сущность живёт сама и переиспользуется — отдельная коллекция + ссылка.
- Рост массива без верха и лимит 16 МБ — сигнал вынести в отдельную коллекцию.
- Многодокументные транзакции (с 4.0+) — проектируйте границы осознанно.
8. Валидация на сервере
db.createCollection("orders", {
validator: {
$jsonSchema: {
bsonType: "object",
required: ["customerId", "status", "createdAt"],
properties: {
customerId: { bsonType: "string" },
status: { enum: ["new", "paid", "shipped", "cancelled"] },
createdAt: { bsonType: "date" }
}
}
},
validationLevel: "strict",
validationAction: "error"
})
9. Транзакции (кратко)
const session = db.getMongo().startSession()
session.startTransaction()
try {
db.orders.insertOne({ ... }, { session })
db.inventory.updateOne({ sku: "A1" }, { $inc: { qty: -1 } }, { session })
session.commitTransaction()
} catch (e) {
session.abortTransaction()
throw e
} finally {
session.endSession()
}
На sharded cluster есть ограничения и накладные расходы — см. гайд по шардированию.
10. Типичные ошибки
| Ошибка | Почему больно |
|---|---|
| Нет индекса под фильтр + сортировку | COLLSCAN |
| Огромные массивы в одном документе | Лимит 16 МБ, дорогие обновления |
$regex без якоря | Не использует индекс по префиксу |
| «Без схемы» = без договорённостей | Хаос в данных |
| Игнорировать writeConcern / readConcern | Потеря или устаревшие чтения при сбоях |