Документы и коллекции — Примеры
Пример 1 — Какие связи встраивать
Проектируются запись пользователя и запись заказа. Строки заказа читаются вместе с заказом и должны меняться вместе с ним; у пользователя один адрес; пользователь принадлежит организации, которую делят многие. Для каждой связи решите, встраивать её или оформлять ссылкой, и покажите документы.
заказ -> его строки (читаются вместе, их немного)
пользователь -> его адрес (читаются вместе, один)
пользователь -> его организация (делится многими, обновляется независимо)
Решение
// один документ на заказ: строки меняются вместе с заказом, поэтому одна запись атомарна
{
"_id": 8842,
"public_id": "ord_8842_pub",
"customer_id": 41,
"items": [
{ "sku": "A-1", "qty": 2 },
{ "sku": "B-7", "qty": 1 }
],
"placed_at": "2026-05-01T09:12:00Z"
}
// один документ на пользователя: адрес ограничен и читается вместе с пользователем
{
"_id": 41,
"name": "Sam",
"address": { "line1": "1 Mill Lane", "city": "Leeds" },
"organisation_id": 7 // ссылка: разделяется, обновляется по своему расписанию
}
Встраивание строк и адреса означает, что одно чтение возвращает всю запись, а изменение их атомарно вместе с родителем; на организацию же ссылаются, потому что данные разделяются и обновляются независимо, — поэтому её чтение стоит второго запроса, а ссылку обеспечивает приложение. Однако обычно ошибаются, встраивая набор, который может расти, — журнал аудита, список сообщений, — пока он не упрётся в потолок размера.
Пример 2 — Две сессии, одно потерянное обновление
У первой строки заказа меняется количество, пока другая сессия исправляет город доставки в том же заказе. Первый путь кода читает весь документ, правит одно вложенное поле и записывает его обратно, и другое изменение исчезает. Измените запись так, чтобы затронутым могло оказаться только то поле, которое она и собирается менять.
const order = await orders.findOne({ _id: 8842 });
order.items[0].qty = 3;
await orders.replaceOne({ _id: 8842 }, order);
// тем временем другая сессия задаёт address.city в том же заказе;
// после этой записи это изменение исчезло.
Решение
await orders.updateOne(
{ _id: 8842, "items.0.sku": "A-1" },
{ $set: { "items.0.qty": 3 } } // затрагивает только этот путь
);
// изменение города, сделанное любой из сессий, не тронуто.
Обновление доходит до одного пути и оставляет каждое другое поле таким, каким его нашло, поэтому две сессии, меняющие разные поля, больше не перезаписывают друг друга; замена всего документа безопасна, только когда у клиента на руках весь документ. Запись в один документ атомарна — поэтому поля, которые должны меняться вместе, принадлежат ему. Обычная ошибка — править документ, загруженный мгновение назад, потому что всё остальное в нём могло измениться с тех пор.