Создание, редактирование и удаление данных. Индексы
Создание документов в MongoDB
Работа с базой данных начинается с вставки (insert). В MongoDB для этого используются методы insertOne
и insertMany
.
insertOne
— вставка одного документа
Чтобы добавить один документ в коллекцию, используйте insertOne. Если вы не укажете _id
, MongoDB сгенерирует его автоматически:
db.movies.insertOne({ title: "Stand by Me" });
В результате документ попадёт в коллекцию movies, а поле _id
будет создано автоматически.
После выполнения можно проверить:
db.movies.find();
insertMany
— массовая вставка
Если нужно вставить несколько документов сразу, используйте insertMany
. Это быстрее и эффективнее, чем вставлять документы по одному:
db.movies.drop(); // Очистим коллекцию перед вставкой
db.movies.insertMany([{ title: "Ghostbusters" }, { title: "E.T." }, { title: "Blade Runner" }]);
После выполнения можно проверить:
db.movies.find();
Внимание
В MongoDB максимальный размер одного запроса — 48 МБ. Если массив слишком большой, драйвер может разделить его на части.
Проверка вставки документов
MongoDB выполняет минимальную проверку данных при вставке. Она гарантирует, что:
- документ имеет корректную BSON-структуру,
- его размер не превышает 16 мегабайт,
- поле
_id
будет добавлено, если отсутствует.
Это означает, что база данных доверяет тому, что вы вставляете, — и именно поэтому важно ограничивать доступ к MongoDB только надежным клиентам, таким как сервер вашего приложения.
Ограничение по размеру: 16 МБ
Один документ в MongoDB не может превышать 16 мегабайт. Это ограничение введено для предотвращения неэффективного проектирования (например, когда вы пытаетесь засунуть слишком много связанных данных в один документ). Для сравнения: весь текст «Войны и мира» занимает примерно 3.14 МБ — это менее четверти допустимого размера одного документа.
Возможность вставить «неправильные» данные
Так как проверка минимальна, MongoDB позволяет вставить логически некорректные данные (например, странные поля, несовместимые структуры и т.д.). Поэтому всегда следует:
- проверять данные на уровне приложения;
- не давать прямой доступ к БД сторонним клиентам;
- использовать валидацию схем JSON при необходимости.
Удаление документов
MongoDB предлагает два метода:
deleteOne()
— удаляет только один документ, соответствующий фильтру;deleteMany()
— удаляет все документы, соответствующие фильтру.
Оба метода принимают фильтр в виде JSON-объекта, аналогично find()
.
deleteOne
Удалим один документ по _id
:
db.movies.find();
Удалим по _id
и сразу проверим результата.
Внимание
Идентификатор документа не будет совпадать с указанным здесь!
db.movies.deleteOne({ _id: ObjectId("682058f73408044d94390553") });
db.movies.find();
Внимание
Если фильтр соответствует нескольким документам, deleteOne()
удалит первый попавшийся. Этот выбор зависит от порядка вставки, индексов и других факторов.
deleteMany
В отличии от deleteOne()
, который удаляет первый попавшийся документ, deleteMany
удаляет все документы, соответствующие фильтру.
Чтобы удалить все документы, используйте deleteMany()
с пустым фильтром:
db.movies.deleteMany({});
Обновление документов
После того как документ сохранён в базе данных, его можно изменить с помощью одного из нескольких методов: updateOne
, updateMany
и replaceOne
.
Методы updateOne
и updateMany
принимают в качестве первого параметра фильтр — документ, задающий критерии поиска, и в качестве второго параметра — модификатор, описывающий, какие изменения нужно внести в найденные документы.
Метод replaceOne
также принимает фильтр как первый параметр, но в отличие от других методов, в качестве второго параметра ожидает полный документ, который заменит существующий.
Обновление документа в MongoDB происходит атомарно: если два обновления выполняются одновременно, сначала будет применено то, которое первым дошло до сервера, затем второе. Это означает, что можно безопасно отправлять конфликтующие обновления подряд, не опасаясь повреждения данных — последнее обновление «победит».
Если вам не подходит поведение «последнее изменение перезаписывает всё», стоит рассмотреть использование шаблона версионности документов.
Замена документа
Метод replaceOne
используется в MongoDB для полной замены одного документа новым. Это особенно полезно при существенных изменениях структуры, например, при миграции схемы. Вместо поэтапного обновления отдельных полей можно просто заменить старый документ новым, сохранив при этом _id
.
Рассмотрим такой пример. Допустим, у нас есть коллекция profiles
, в которой документы выглядят так:
db.profiles.insertMany([
{
_id: 1001,
name: "alex",
age: 30,
city: "Moscow",
hobbies_count: 5,
followers_count: 200,
},
{
_id: 1002,
name: "kate",
age: 25,
city: "Berlin",
hobbies_count: 3,
followers_count: 120,
},
]);
Теперь допустим, мы хотим изменить структуру: убрать поля name
, hobbies_count
, followers_count
и вместо них добавить username
и вложенный поддокумент stats
. Это делается одной командой replaceOne
:
db.profiles.replaceOne(
{ _id: 1001 },
{
age: 30,
city: "Moscow",
username: "alex",
stats: {
hobbies: 5,
followers: 200,
},
}
);
Начало работы с модификатором $set
Модификатор $set
используется для установки значения поля в документе. Если поле ещё не существует — оно будет создано. Это особенно удобно при добавлении новых полей, обновлении структуры или персонализации профилей пользователей.
Предположим, в коллекции users
есть следующий документ:
db.users.insertOne({
_id: 5001,
username: "maria",
age: 27,
country: "Poland",
});
Теперь пользователь хочет добавить поле favorite_color
— это можно сделать с помощью $set
:
db.users.updateOne({ _id: 5001 }, { $set: { favorite_color: "blue" } });
После этого документ будет выглядеть так:
{
"_id": 5001,
"username": "maria",
"age": 27,
"country": "Poland",
"favorite_color": "blue"
}
Если позже она решит, что больше любит другой цвет, $set
можно снова использовать для обновления:
db.users.updateOne({ username: "maria" }, { $set: { favorite_color: "green" } });
Модификатор $set
позволяет также изменять тип значения. Например, если пользователь решает, что у неё несколько любимых цветов, можно заменить строку на массив:
db.users.updateOne({ username: "maria" }, { $set: { favorite_color: ["green", "purple", "orange"] } });
Если же она решит, что не хочет указывать цвет вообще, поле можно полностью удалить с помощью модификатора $unset
:
db.users.updateOne({ username: "maria" }, { $unset: { favorite_color: 1 } });
После этого поле favorite_color
будет удалено, и документ вернётся к прежнему виду.
Модификатор $set
также используется для обновления вложенных полей. Например, пусть в коллекции articles
хранится пост с вложенным полем author
:
db.articles.insertOne({
_id: 8001,
title: "MongoDB Basics",
content: "Introduction to MongoDB...",
author: {
name: "Ivan",
email: "ivan@example.com",
},
});
Если нужно обновить имя автора, можно сделать это так:
db.articles.updateOne({ "author.name": "Ivan" }, { $set: { "author.name": "Ivan Petrov" } });
После этого имя будет изменено только внутри вложенного объекта author
, остальные поля документа сохранятся:
{
"_id": 8001,
"title": "MongoDB Basics",
"content": "Introduction to MongoDB...",
"author": {
"name": "Ivan Petrov",
"email": "ivan@example.com"
}
}
Внимание
Всегда используйте $
-модификаторы ($set
, $unset
и т.д.) при обновлении.
Частая ошибка — попытка обновить документ без модификатора:
// Ошибочный подход!
db.articles.updateOne({ "author.name": "Ivan Petrov" }, { "author.name": "Ivan Sidorov" });
Это приведёт к ошибке, потому что MongoDB ожидает обновление через оператор (например, $set
), а не целый документ без модификаторов. В старых версиях API это не вызывало ошибку, а просто полностью заменяло документ — часто с неожиданными последствиями. Сейчас это считается некорректным.
Используйте модификаторы строго и осознанно — они дают контроль над обновлениями на уровне отдельных полей, без замены всего документа.
Инкрементирование и декрементирование
Модификатор $inc
используется для увеличения или уменьшения числового значения поля. Если поле уже существует и содержит число — его значение будет изменено. Если поле не существует, оно будет создано и инициализировано перед добавлением значения.
Это очень удобно для обновления статистики, счётчиков, рейтингов, голосов или любых других числовых значений.
Предположим, мы создаём коллекцию scores
, в которой будем отслеживать баллы пользователей в разных играх. Начнём с того, что добавим запись о том, что пользователь anna начала играть в игру "tetris":
db.scores.insertOne({ game: "tetris", user: "anna" });
Теперь, когда игрок набирает очки, мы можем использовать $inc
, чтобы прибавить их к полю points
. Допустим, пользователь заработал 150
очков:
db.scores.updateOne({ game: "tetris", user: "anna" }, { $inc: { points: 150 } });
Если теперь посмотреть на документ, он будет выглядеть так:
{
"_id": ObjectId("..."),
"game": "tetris",
"user": "anna",
"points": 150
}
Обратите внимание: поле points
не существовало в момент вставки, но MongoDB создала его автоматически и установила значение 150
— это как раз сумма приращения.
Если пользователь получает дополнительный бонус, например, +5000
очков, можно просто снова использовать $inc
:
db.scores.updateOne({ game: "tetris", user: "anna" }, { $inc: { points: 5000 } });
Теперь документ будет выглядеть так:
{
"_id": ObjectId("..."),
"game": "tetris",
"user": "anna",
"points": 5150
}
Если вы хотите не увеличивать, а уменьшить значение, просто передайте отрицательное число. Например, если игрок теряет 100
очков:
db.scores.updateOne({ game: "tetris", user: "anna" }, { $inc: { points: -100 } });
Итоговое значение points будет равно 5050
.
Однако $inc
работает только с числовыми значениями. Если попытаться применить его к полю другого типа, будет ошибка. Например, создадим коллекцию clicks
с полем в виде строки:
db.clicks.insertOne({ count: "1" });
Теперь пробуем выполнить $inc
:
db.clicks.updateOne({}, { $inc: { count: 1 } });
MongoDB выдаст ошибку:
Cannot apply $inc to a value of non-numeric type.
... has the field 'count' of non-numeric type string
Аналогично, нельзя увеличивать значение на строку или массив — значение приращения тоже должно быть числом.
Операторы массива
MongoDB предоставляет обширный набор операторов для работы с массивами. Массивы — это мощный способ хранения списков, вложенных данных, а также наборов значений. С их помощью можно не только добавлять и удалять элементы, но и обновлять конкретные позиции, отслеживать уникальность и управлять длиной списка.
Добавление элементов
Оператор $push
используется для добавления значений в конец массива. Если указанный массив ещё не существует в документе, MongoDB автоматически создаст его и добавит туда значение. Это делает $push
удобным для постепенного накопления данных — например, комментариев, логов или истории изменений.
Представим, что мы храним коллекцию с информацией о книгах, и хотим начать добавлять к каждой книге рецензии в виде массива объектов. Изначально документ книги выглядит так:
db.books.insertOne({
title: "Clean Code",
author: "Robert C. Martin",
});
Теперь добавим первую рецензию с помощью $push
:
db.books.updateOne(
{ title: "Clean Code" },
{
$push: {
reviews: {
user: "alice",
rating: 9,
comment: "A must-read for every developer.",
},
},
}
);
После этого документ будет выглядеть так:
{
"_id" : ObjectId("664b9e8f12abc12345678901"),
"title" : "Clean Code",
"author" : "Robert C. Martin",
"reviews" : [
{
"user" : "alice",
"rating" : 9,
"comment" : "A must-read for every developer."
}
]
}
Теперь добавим ещё одну рецензию от другого пользователя:
db.books.updateOne(
{ title: "Clean Code" },
{
$push: {
reviews: {
user: "bob",
rating: 8,
comment: "Great insights, though some chapters felt dated.",
},
},
}
);
И после этой команды структура массива станет такой:
{
"_id" : ObjectId("664b9e8f12abc12345678901"),
"title" : "Clean Code",
"author" : "Robert C. Martin",
"reviews" : [
{
"user" : "alice",
"rating" : 9,
"comment" : "A must-read for every developer."
},
{
"user" : "bob",
"rating" : 8,
"comment" : "Great insights, though some chapters felt dated."
}
]
}
Это — простейшая форма использования $push
. Однако MongoDB позволяет модифицировать поведение добавления с помощью дополнительных операторов, таких как $each
, $slice
и $sort
.
Добавление нескольких значений с $each
Если вам нужно добавить сразу несколько значений в массив, используйте оператор $each
в составе $push
. Допустим, у нас есть коллекция products
, в которой мы храним данные о товарах. Добавим туда новый документ:
Теперь добавим сразу три новых значения в массив price_history
:
db.products.updateOne(
{ _id: "notebook" },
{
$push: {
price_history: {
$each: [789.99, 799.99, 809.99],
},
},
}
);
В массив price_history
будут добавлены три цены — по порядку, как указано в массиве $each
.
Ограничение длины массива с $slice
Допустим, вы хотите хранить не все значения, а только последние 5. Тогда добавьте модификатор $slice
, указывающий, сколько последних элементов сохранить. Например, ограничим историю цен:
db.products.updateOne(
{ _id: "notebook" },
{
$push: {
price_history: {
$each: [819.99, 829.99],
$slice: -5,
},
},
}
);
После этой операции в price_history
останутся только последние 5 цен. Если до вставки было 4 значения, массив расширится до 6, но $slice
: -5 обрежет его, сохранив только последние пять. Если в $slice
передать положительное число, MongoDB сохранит первые N
элементов массива после применения $each
.
Сортировка элементов массива с $sort
MongoDB позволяет сортировать массив при добавлении элементов, если вы работаете с массивом объектов. Допустим, у вас есть массив с рейтингами фильмов. Сначала добавим базовый документ:
db.ratings.insertOne({
category: "comedy",
top_movies: [
{ title: "The Grand Budapest Hotel", score: 8.1 },
{ title: "Groundhog Day", score: 8.0 },
{ title: "Dr. Strangelove", score: 8.4 },
{ title: "The Big Lebowski", score: 8.1 },
{ title: "Monty Python and the Holy Grail", score: 8.2 },
{ title: "Superbad", score: 7.6 },
{ title: "The Mask", score: 8.3 },
{ title: "Anchorman", score: 7.2 },
{ title: "Shaun of the Dead", score: 7.9 },
{ title: "Dumb and Dumber", score: 7.3 },
],
});
Добавим два фильма и отсортируем их по рейтингу по убыванию:
db.ratings.updateOne(
{ category: "comedy" },
{
$push: {
top_movies: {
$each: [
{ title: "Hot Fuzz", score: 7.8 },
{ title: "The Nice Guys", score: 7.4 },
],
$sort: { score: -1 },
$slice: -10,
},
},
}
);
После выполнения команды:
в массив
top_movies
добавятся два объекта;MongoDB отсортирует массив по убыванию поля
score
;если в массиве более 10 элементов — останутся только лучшие 10.
Внимание
Если вы используете $sort
или $slice
, вы обязательно должны использовать и $each
. Иначе операция будет некорректной — MongoDB не применяет $sort
и $slice
к одному значению без $each
.
Использование массивов в качестве множеств
Иногда массив в документе следует рассматривать не как список с дублирующимися значениями, а как множество — то есть структуру, где каждое значение уникально. В MongoDB этого можно добиться с помощью двух подходов: условия $ne
и оператора $addToSet
.
Подход с $ne
Предположим, у нас есть коллекция citations
, и каждый документ в ней содержит массив authors_cited
, где мы хотим хранить имена авторов, на которых сослались в публикации. Мы хотим добавить автора в этот массив, но только если его там ещё нет.
Создадим исходный документ:
db.citations.insertOne({
paper_id: "paper_001",
authors_cited: ["Codd", "Date"],
});
Теперь добавим нового автора "Richie", только если его ещё нет в массиве:
db.citations.updateOne(
{ paper_id: "paper_001", authors_cited: { $ne: "Richie" } },
{ $push: { authors_cited: "Richie" } }
);
Если "Richie" уже есть в массиве, обновление не выполнится — проверка $ne
этому помешает.
Подход с $addToSet
Однако более надёжный и читаемый способ — это использование оператора $addToSet
, который сам гарантирует отсутствие дублирования. Например, у нас есть пользователь с набором email-адресов.
Сначала создадим документ:
db.users.insertOne({
_id: 777,
username: "joe",
emails: ["joe@example.com", "joe@gmail.com", "joe@yahoo.com"],
});
Попробуем добавить существующий адрес "joe@gmail.com" с помощью $addToSet
:
db.users.updateOne({ _id: 777 }, { $addToSet: { emails: "joe@gmail.com" } });
Поскольку такой адрес уже есть, modifiedCount
будет равен 0 — обновление проигнорировано.
Теперь добавим новый адрес "joe@hotmail.com":
db.users.updateOne({ _id: 777 }, { $addToSet: { emails: "joe@hotmail.com" } });
Теперь поле emails будет содержать:
["joe@example.com", "joe@gmail.com", "joe@yahoo.com", "joe@hotmail.com"]
Добавление нескольких уникальных значений
Если вы хотите добавить сразу несколько адресов и при этом избежать дубликатов, используйте $each
внутри $addToSet
. Это то, чего нельзя добиться с $push
+ $ne
.
Пример:
db.users.updateOne(
{ _id: 777 },
{
$addToSet: {
emails: {
$each: ["joe@php.net", "joe@example.com", "joe@python.org"],
},
},
}
);
После этого массив emails будет содержать:
["joe@example.com", "joe@gmail.com", "joe@yahoo.com", "joe@hotmail.com", "joe@php.net", "joe@python.org"]
Обратите внимание: "joe@example.com" не был добавлен повторно, так как уже присутствовал в массиве. Остальные два — были добавлены.
Оператор $addToSet
особенно полезен при работе с массивами, где важна уникальность, и он помогает избавиться от лишней проверки вручную.
Удаление элементов из массива
MongoDB предоставляет несколько способов удаления элементов из массива, в зависимости от того, хотите ли вы удалить по позиции или по значению.
Удаление по позиции: оператор $pop
Если вы хотите рассматривать массив как очередь или стек, вы можете использовать оператор $pop
, который удаляет один элемент — либо с начала, либо с конца массива.
{"$pop": {"key": -1}}
— удаляет первый элемент (начало массива).{"$pop": {"key": 1}}
— удаляет последний элемент (конец массива).
Пример. Допустим, у нас есть список покупок:
db.shopping.insertOne({
user: "alice",
cart: ["bread", "milk", "eggs", "butter"],
});
Удалим последний товар:
db.shopping.updateOne({ user: "alice" }, { $pop: { cart: 1 } });
Теперь cart
будет содержать:
["bread", "milk", "eggs"]
А если хотим удалить первый товар:
db.shopping.updateOne({ user: "alice" }, { $pop: { cart: -1 } });
Теперь массив сократится до:
["milk", "eggs"]
Удаление по значению: оператор $pull
Если вам нужно удалить определённое значение, независимо от его позиции, используйте $pull
. Этот оператор удаляет все вхождения указанного значения или соответствующие условию.
Создадим список дел:
db.tasks.insertOne({
user: "bob",
todo: ["dishes", "laundry", "dry cleaning"],
});
Если мы закончили со стиркой, удалим задачу "laundry":
db.tasks.updateOne({ user: "bob" }, { $pull: { todo: "laundry" } });
Теперь список дел:
["dishes", "dry cleaning"]
Если в массиве есть несколько одинаковых значений, все они будут удалены. Например:
db.test.insertOne({ values: [1, 1, 2, 1] });
Удалим все единицы:
db.test.updateOne({}, { $pull: { values: 1 } });
Результат:
[2]
Внимание
$pull
работает только с массивами. Если вы примените его к полю, которое содержит строку, число или объект, MongoDB вернёт ошибку. То же касается $push
, $pop
и других операторов, предназначенных только для массивов. Если вы работаете со скалярными значениями (например, строками или числами, а не массивами), используйте обычные операторы $set
и $inc
.
Модификации элементов массива по позиции
Иногда вам нужно обновить конкретный элемент массива, особенно если это массив вложенных документов — например, комментарии к посту. MongoDB предоставляет два подхода:
Обращение по индексу (если вы точно знаете позицию);
Использование позиционного оператора
$
, если вы знаете условие, но не позицию.
Обновление по индексу
Массивы в MongoDB индексируются с нуля, как обычные списки. Вы можете обратиться к конкретному элементу массива по номеру.
Предположим, у нас есть документ с отзывами на статью:
db.articles.insertOne({
_id: "post1",
title: "How to use MongoDB",
comments: [
{ author: "Anna", text: "Very helpful", likes: 2 },
{ author: "Ben", text: "Needs more examples", likes: 1 },
{ author: "Chris", text: "Great article", likes: 3 },
],
});
Допустим, мы хотим увеличить число лайков у первого комментария (индекс 0):
db.articles.updateOne({ _id: "post1" }, { $inc: { "comments.0.likes": 1 } });
Теперь у Анны будет 3 лайка.
Обновление по условию: позиционный оператор $
Иногда вы не знаете, где именно находится нужный элемент. Например, вы хотите изменить имя автора комментария с "Ben" на "Benjamin", но не хотите предварительно загружать весь документ и считать индекс.
Для этого существует позиционный оператор $
, который позволяет MongoDB найти первый подходящий элемент в массиве и применить обновление:
db.articles.updateOne({ "comments.author": "Ben" }, { $set: { "comments.$.author": "Benjamin" } });
Обратите внимание: comments.$.author
— это путь к нужному полю внутри того элемента, который соответствует фильтру "comments.author": "Ben".
Если Бен оставил несколько комментариев, будет обновлён только первый. MongoDB всегда применяет $
к первому совпадению в массиве. Если фильтр ("comments.author": "Ben") не находит ни одного подходящего элемента, ничего не изменится.
Позиционный оператор $
не может использоваться без фильтра, который указывает на элемент массива — он всегда должен быть "привязан" к тому, что вы ищете.