В прошлом уроке мы раскладывали связи по способам хранения и заметили, что книга держит копию имени автора, хотя у автора есть своя коллекция. Сегодня разберём, зачем данные копируют, чем за это платят и какие копии делать безопасно. Все примеры на учебной базе магазина.
Визитка в записной книжке
Друг дал тебе визитку, и ты переписал его телефон в три места: в телефон, в рабочий ежедневник и на листок у холодильника. Звонить стало удобно, номер всегда под рукой. Но друг сменил номер. Теперь его нужно исправить в трёх местах, и если забудешь про листок, однажды позвонишь не туда.
Это и есть денормализация: одни и те же данные хранятся в нескольких местах, чтобы быстрее читать. Расплата приходит при записи.
В реляционной теории денормализация это исключение, на которое идут осознанно и редко. Мы обсуждали это в уроке про BCNF и денормализацию. В MongoDB это обычный рабочий инструмент: соединений в привычном виде нет, поэтому данные, которые нужны на странице, кладут в документ этой страницы.
Копия сводки: extended reference
В учебной базе книга хранит не только номер автора, но и его имя и страну:
db.books.findOne({ _id: 3 }, { title: 1, author: 1 })
{
_id: 3,
title: 'Пикник на обочине',
author: { _id: 2, name: 'Стругацкие', country: 'Россия' }
}
Это расширенная ссылка: ссылка _id плюс копия полей, которые чаще всего нужны вместе с ней. Витрина показывает книги с именем автора, и благодаря копии ей хватает одного запроса без похода в authors.
Теперь друг меняет номер. Магазин решил писать авторов полностью: «Аркадий и Борис Стругацкие». Сначала правим оригинал:
db.authors.updateOne({ _id: 2 }, { $set: { name: 'Аркадий и Борис Стругацкие' } })
{
acknowledged: true,
insertedId: null,
matchedCount: 1,
modifiedCount: 1,
upsertedCount: 0
}
Потом каждую копию. Копии находим по ссылке author._id:
db.books.updateMany(
{ 'author._id': 2 },
{ $set: { 'author.name': 'Аркадий и Борис Стругацкие' } }
)
{
acknowledged: true,
insertedId: null,
matchedCount: 2,
modifiedCount: 2,
upsertedCount: 0
}
Две книги, две копии. Проверим:
db.books.find({ 'author._id': 2 }, { title: 1, 'author.name': 1 }).sort({ _id: 1 })
[
{
_id: 3,
title: 'Пикник на обочине',
author: { name: 'Аркадий и Борис Стругацкие' }
},
{
_id: 4,
title: 'Трудно быть богом',
author: { name: 'Аркадий и Борис Стругацкие' }
}
]
Вот и цена копии:
- Изменение стало двумя командами. Если приложение упадёт между ними, автор будет называться по-новому, а книги по-старому. Такое расхождение называют рассинхронизацией. Связать две команды в одно целое помогают многодокументные транзакции, им посвящён отдельный урок ближе к концу раздела.
- Запись стала дороже. У автора с сотней книг одна правка имени превращается в сотню обновлений.
Отсюда правило: копируй поля, которые читают часто, а меняют редко. Имя автора и страна меняются раз в жизни, поэтому копировать их выгодно. А цену или остаток на складе копировать в чужие документы нельзя: они меняются каждый день, и копии будут вечно отставать.
урок из курса
Урок из курса «MongoDB с нуля». Курс покупается один раз и остаётся навсегда: 500 ₽.
Оплата пока не подключена: премиум и курсы открываются промокодом.