Введение
В этой лабораторной работе вы научитесь использовать ссылки MongoDB для моделирования отношений между данными. Вы создадите простую систему управления библиотекой с коллекциями authors и books. Выполняя практические шаги, вы научитесь создавать документы, связывать их с помощью ссылок, запрашивать связанные данные из разных коллекций, обновлять эти ссылки и повышать производительность запросов с помощью индексов. Эта лабораторная работа закладывает практическую основу для моделирования данных в MongoDB.
Создание коллекций и ссылочных документов
На этом этапе вы настроите свою базу данных и создадите две коллекции: authors и books. Вы изучите фундаментальную концепцию ссылок между документами, связав книгу с ее автором.
Сначала откройте MongoDB Shell. Эта интерактивная оболочка — место, где вы будете выполнять все команды базы данных.
mongosh
Оказавшись в оболочке, вы увидите приглашение test>. Переключитесь на новую базу данных с именем library_db. Если база данных не существует, MongoDB создаст ее при первом сохранении данных.
use library_db
Теперь создайте своего первого автора. Вставьте документ в коллекцию authors. Мы указываем пользовательский _id для этого автора, чтобы его было легко ссылаться позже.
db.authors.insertOne({
_id: ObjectId("6633c9a5b4e3e8a5c8a8f8b1"),
name: "Jane Austen",
nationality: "British",
birthYear: 1775
})
Далее вставьте документ в коллекцию books. Поле author_id содержит ObjectId только что созданного вами автора. Именно так создается ссылка.
db.books.insertOne({
title: "Pride and Prejudice",
author_id: ObjectId("6633c9a5b4e3e8a5c8a8f8b1"),
published: 1813,
genre: "Classic Literature"
})
Теперь вы создали отношение "один к одному". Чтобы проверить это, вы можете получить только что созданные документы.
Сначала найдите автора:
db.authors.findOne({ name: "Jane Austen" })
Пример вывода:
{
_id: ObjectId("6633c9a5b4e3e8a5c8a8f8b1"),
name: 'Jane Austen',
nationality: 'British',
birthYear: 1775
}
Теперь найдите книгу и обратите внимание на поле author_id, которое ссылается на автора.
db.books.findOne({ title: "Pride and Prejudice" })
Пример вывода:
{
_id: ObjectId("..."),
title: 'Pride and Prejudice',
author_id: ObjectId("6633c9a5b4e3e8a5c8a8f8b1"),
published: 1813,
genre: 'Classic Literature'
}
Вы можете оставаться в оболочке mongosh для следующих шагов.
Связывание нескольких документов
Автор обычно пишет более одной книги. На этом этапе вы научитесь связывать несколько "дочерних" документов (книг) с одним "родительским" документом (автором). Это демонстрирует отношение "один ко многим".
Продолжайте работать в оболочке mongosh. Давайте добавим еще две книги Джейн Остин. Используйте команду insertMany для одновременной вставки нескольких документов. Обе новые книги будут ссылаться на один и тот же author_id.
db.books.insertMany([
{
title: "Sense and Sensibility",
author_id: ObjectId("6633c9a5b4e3e8a5c8a8f8b1"),
published: 1811,
genre: "Classic Literature"
},
{
title: "Emma",
author_id: ObjectId("6633c9a5b4e3e8a5c8a8f8b1"),
published: 1815,
genre: "Classic Literature"
}
])
Теперь, когда у Джейн Остин есть три книги в нашей базе данных, получите их все, используя метод find() и фильтруя по author_id.
db.books.find({ author_id: ObjectId("6633c9a5b4e3e8a5c8a8f8b1") })
Пример вывода:
[
{
_id: ObjectId("..."),
title: 'Pride and Prejudice',
author_id: ObjectId("6633c9a5b4e3e8a5c8a8f8b1"),
published: 1813,
genre: 'Classic Literature'
},
{
_id: ObjectId("..."),
title: 'Sense and Sensibility',
author_id: ObjectId("6633c9a5b4e3e8a5c8a8f8b1"),
published: 1811,
genre: 'Classic Literature'
},
{
_id: ObjectId("..."),
title: 'Emma',
author_id: ObjectId("6633c9a5b4e3e8a5c8a8f8b1"),
published: 1815,
genre: 'Classic Literature'
}
]
Вы также можете быстро подсчитать, сколько книг связано с конкретным автором, используя countDocuments.
db.books.countDocuments({ author_id: ObjectId("6633c9a5b4e3e8a5c8a8f8b1") })
Пример вывода:
3
Этот простой запрос эффективно подтверждает количество связанных документов.
Запросы между коллекциями с помощью $lookup
До сих пор вы извлекали книги, используя известный author_id. Более мощный подход — объединить данные из обеих коллекций в одном запросе. На этом этапе вы будете использовать стадию агрегации $lookup для выполнения левого внешнего соединения (left outer join) из коллекции books в коллекцию authors.
Сначала добавим еще одного автора и книгу, чтобы сделать наш запрос более интересным.
db.authors.insertOne({
_id: ObjectId("6633c9a5b4e3e8a5c8a8f8b2"),
name: "Charles Dickens",
nationality: "British",
birthYear: 1812
})
db.books.insertOne({
title: "Oliver Twist",
author_id: ObjectId("6633c9a5b4e3e8a5c8a8f8b2"),
published: 1837,
genre: "Historical Fiction"
})
Теперь построим конвейер агрегации (aggregation pipeline). Этот запрос начнет с коллекции books и "найдет" соответствующего автора для каждой книги.
db.books.aggregate([
{
$lookup: {
from: "authors",
localField: "author_id",
foreignField: "_id",
as: "author_details"
}
}
])
Стадия $lookup имеет следующие поля:
from: "authors": Указывает коллекцию для соединения.localField: "author_id": Поле из входных документов (изbooks).foreignField: "_id": Поле из документов коллекции "from" (изauthors).as: "author_details": Имя нового поля-массива, которое добавляется к входным документам.
Пример вывода (для одного документа):
{
_id: ObjectId("..."),
title: 'Pride and Prejudice',
author_id: ObjectId("6633c9a5b4e3e8a5c8a8f8b1"),
published: 1813,
genre: 'Classic Literature',
author_details: [
{
_id: ObjectId("6633c9a5b4e3e8a5c8a8f8b1"),
name: 'Jane Austen',
nationality: 'British',
birthYear: 1775
}
]
}
Как вы можете видеть, информация об авторе теперь встроена в каждый документ книги под полем author_details. Это позволяет одновременно выполнять запросы по полям из обеих коллекций.
Обновление и поддержка ссылок
Данные не всегда статичны. Возможно, вам потребуется исправить ошибки или удалить данные, что потребует обновления или удаления документов и их ссылок. На этом этапе вы научитесь обновлять ссылку и обрабатывать "осиротевшие" документы.
Представьте, что вы обнаружили, что книга "Emma" была ошибочно приписана Джейн Остин и должна быть назначена Чарльзу Диккенсу. Вы можете исправить это с помощью команды updateOne с оператором $set.
db.books.updateOne(
{ title: "Emma" },
{ $set: { author_id: ObjectId("6633c9a5b4e3e8a5c8a8f8b2") } }
)
Проверьте изменение, снова найдя книгу и проверив ее author_id.
db.books.findOne({ title: "Emma" })
Пример вывода:
{
_id: ObjectId("..."),
title: 'Emma',
author_id: ObjectId("6633c9a5b4e3e8a5c8a8f8b2"),
published: 1815,
genre: 'Classic Literature'
}
Теперь давайте посмотрим, что происходит при удалении родительского документа. Если мы удалим автора, любые книги, ссылающиеся на этого автора, станут "осиротевшими". Давайте удалим Чарльза Диккенса из нашей базы данных.
db.authors.deleteOne({ name: "Charles Dickens" })
Документ автора удален, но книги "Emma" и "Oliver Twist" по-прежнему имеют author_id, указывающий на удаленного автора. Это может привести к проблемам с целостностью данных. В реальном приложении вы бы реализовали логику для обработки этого, например, удаление осиротевших книг или их переназначение.
Для этой лабораторной работы давайте вручную выполним очистку, удалив две осиротевшие книги.
db.books.deleteMany({ author_id: ObjectId("6633c9a5b4e3e8a5c8a8f8b2") })
Эта команда удаляет все документы из коллекции books, которые ссылаются на теперь уже удаленного автора, обеспечивая согласованность наших данных.
Повышение производительности запросов с помощью индексов
Когда ваши коллекции растут, запросы, фильтрующие по определенному полю, могут стать медленными. Это происходит потому, что MongoDB приходится сканировать каждый документ для поиска совпадений. Для оптимизации этого процесса вы можете создать индекс для полей, по которым вы часто выполняете запросы. В нашем случае author_id в коллекции books является идеальным кандидатом.
На этом этапе вы создадите индекс для поля author_id, чтобы ускорить поиск книг автора.
Используйте команду createIndex для коллекции books. Аргумент { author_id: 1 } указывает MongoDB создать индекс в порядке возрастания для поля author_id.
db.books.createIndex({ author_id: 1 })
MongoDB обработает это в фоновом режиме. После завершения будет возвращено сообщение, подтверждающее создание индекса.
Пример вывода:
{
"numIndexesBefore": 1,
"numIndexesAfter": 2,
"createdCollectionAutomatically": false,
"ok": 1
}
Чтобы убедиться, что индекс существует, вы можете использовать команду getIndexes. Она выведет список всех индексов в коллекции books.
db.books.getIndexes()
Вы должны увидеть два индекса: индекс по умолчанию для _id и новый индекс author_id_1, который вы только что создали.
Пример вывода:
[
{ "v": 2, "key": { "_id": 1 }, "name": "_id_" },
{ "v": 2, "key": { "author_id": 1 }, "name": "author_id_1" }
]
С этим индексом любой запрос, который фильтрует или сортирует по author_id, включая стадию $lookup, которую вы использовали ранее, будет значительно быстрее на больших наборах данных.
Наконец, вы можете выйти из оболочки MongoDB.
exit
Резюме
В этой лабораторной работе вы изучили основы использования ссылок на документы в MongoDB. Вы начали с создания коллекций и связывания документов с помощью ссылок ObjectId. Затем вы отработали управление отношениями "один ко многим", выполнение запросов между коллекциями с использованием мощной стадии агрегации $lookup и поддержание целостности данных путем обновления и очистки ссылок. Наконец, вы повысили производительность запросов, создав индекс для поля ссылки. Эти навыки необходимы для создания масштабируемых и эффективных приложений с использованием MongoDB.

