Освоение CRUD-операций в MongoDB: практическое руководство по командам

Раскройте возможности MongoDB с помощью этого практического руководства по основным CRUD-операциям. Научитесь эффективно управлять данными, используя команды `insert`, `find`, `update` и `delete`. Статья содержит понятные объяснения, реальные примеры и лучшие практики создания, чтения, обновления и удаления документов в коллекциях MongoDB. Идеально подходит для разработчиков и администраторов — это ваш основной ресурс для освоения манипуляции данными в MongoDB.

Освоение CRUD-операций в MongoDB: практическое руководство по командам

MongoDB, популярная NoSQL документо-ориентированная база данных, является основой для множества современных приложений благодаря своей гибкости, масштабируемости и производительности. В основе взаимодействия с любой базой данных лежат фундаментальные операции создания, чтения, обновления и удаления (CRUD). Освоение этих команд необходимо каждому, кто работает с MongoDB, будь то разработчики, создающие новые функции, или администраторы, управляющие данными.

Это руководство использует небольшую коллекцию users и показывает команды MongoDB CRUD, к которым вы обращаетесь в реальной работе: добавление записи из формы регистрации, поиск документа по тикету поддержки, изменение поля без перезаписи остальной части документа и удаление данных без случайного очищения всей коллекции. Примеры используют mongosh, но те же фильтры и операторы обновления применимы и в драйверах приложений.

Предварительные требования

Прежде чем приступить к командам, убедитесь, что у вас есть:

  • MongoDB установлена и запущена: Вы можете скачать ее с официального сайта MongoDB или использовать облачный сервис, например MongoDB Atlas.
  • Установлен mongosh (MongoDB Shell): Это интерактивный JavaScript-интерфейс для MongoDB.

Подключение к MongoDB и выбор базы данных

Для начала откройте терминал или командную строку и подключитесь к вашему экземпляру MongoDB с помощью mongosh:

mongosh

После подключения вы окажетесь в базе данных test по умолчанию. Чтобы переключиться на другую базу данных или создать новую, используйте команду use:

use myDatabase;

Если база данных myDatabase не существует, MongoDB создаст ее неявно при вставке первого документа в коллекцию внутри нее.

Операции создания (Вставка)

Операции создания включают добавление новых документов в коллекцию. MongoDB предоставляет методы для вставки одного или нескольких документов.

1. db.collection.insertOne()

Этот метод вставляет один документ в коллекцию. Если коллекция не существует, MongoDB создает ее.

// Выбираем коллекцию с именем 'users'
db.users.insertOne({
  name: "Alice Smith",
  age: 30,
  city: "New York",
  email: "[email protected]",
  interests: ["reading", "hiking"]
});

Вывод покажет статус acknowledged и insertedId нового документа.

2. db.collection.insertMany()

Используйте этот метод для вставки нескольких документов в коллекцию за одну операцию. Он принимает массив документов.

db.users.insertMany([
  {
    name: "Bob Johnson",
    age: 24,
    city: "Los Angeles",
    email: "[email protected]",
    interests: ["coding", "gaming"]
  },
  {
    name: "Charlie Brown",
    age: 35,
    city: "New York",
    email: "[email protected]",
    interests: ["cooking", "photography"]
  },
  {
    name: "Diana Prince",
    age: 29,
    city: "London",
    email: "[email protected]",
    interests: ["fitness", "travel"]
  }
]);

Это вернет массив insertedIds для всех добавленных документов.

Совет: MongoDB автоматически добавляет поле _id (уникальный ObjectId) к каждому документу, если вы его не предоставили.

Операции чтения (Поиск)

Операции чтения включают запросы документов из коллекции. Метод find() является вашим основным инструментом для этого.

1. db.collection.find()

a. Найти все документы

Чтобы получить все документы в коллекции, вызовите find() без аргументов:

db.users.find();

b. Найти документы с фильтром запроса

Передайте документ запроса в find(), чтобы указать критерии выбора документов. Это действует как предложение WHERE в SQL.

// Найти пользователей из Нью-Йорка
db.users.find({ city: "New York" });

// Найти пользователей старше 25 лет
db.users.find({ age: { $gt: 25 } });

// Найти пользователей в возрасте от 25 до 35 лет (исключая 35)
db.users.find({ age: { $gt: 25, $lt: 35 } });

// Найти пользователей, чьи интересы включают 'coding'
db.users.find({ interests: "coding" });

// Найти пользователей, чьи интересы включают И 'reading', И 'hiking'
db.users.find({ interests: { $all: ["reading", "hiking"] } });

// Найти пользователей с именем Alice Smith ИЛИ из Лондона
db.users.find({
  $or: [
    { name: "Alice Smith" },
    { city: "London" }
  ]
});

Распространенные операторы запросов:

  • $eq: Равно (по умолчанию, если оператор не указан)
  • $ne: Не равно
  • $gt: Больше чем
  • $gte: Больше или равно
  • $lt: Меньше чем
  • $lte: Меньше или равно
  • $in: Соответствует любому из значений, указанных в массиве
  • $nin: Не соответствует ни одному из значений, указанных в массиве
  • $and, $or, $not, $nor: Логические операторы

c. Проекция: выбор определенных полей

Чтобы вернуть только подмножество полей, передайте документ проекции вторым аргументом в find(). Значение 1 включает поле, 0 исключает его. Поле _id включается по умолчанию, если явно не исключено.

// Вернуть только имя и email, исключить _id
db.users.find({ city: "New York" }, { name: 1, email: 1, _id: 0 });

d. Сортировка, ограничение и пропуск

Цепочка методов позволяет выполнять более сложные запросы:

// Сортировать по возрасту в порядке убывания (1 для возрастания, -1 для убывания)
db.users.find().sort({ age: -1 });

// Ограничить результаты до 2 документов
db.users.find().limit(2);

// Пропустить первые 2 документа и вернуть остальные
db.users.find().skip(2);

// Комбинировать операции: найти пользователей из Нью-Йорка, отсортировать по возрасту, пропустить 1, ограничить 1
db.users.find({ city: "New York" }).sort({ age: 1 }).skip(1).limit(1);

2. db.collection.findOne()

Этот метод возвращает один документ, соответствующий критериям запроса. Если соответствует несколько документов, возвращается первый найденный.

db.users.findOne({ name: "Alice Smith" });

Операции обновления

Операции обновления изменяют существующие документы в коллекции. Вы можете обновить один документ, несколько документов или даже заменить целый документ.

1. db.collection.updateOne()

Обновляет один документ, соответствующий критериям фильтра.

// Обновить город Alice на 'San Francisco'
db.users.updateOne(
  { name: "Alice Smith" },
  { $set: { city: "San Francisco", lastUpdated: new Date() } }
);

2. db.collection.updateMany()

Обновляет все документы, соответствующие критериям фильтра.

// Увеличить возраст всех пользователей в Нью-Йорке на 1
db.users.updateMany(
  { city: "New York" },
  { $inc: { age: 1 } }
);

// Добавить новый интерес 'reading' пользователям, у которых его еще нет
db.users.updateMany(
  {}, // Применить ко всем документам
  { $addToSet: { interests: "reading" } }
);

// Удалить 'gaming' из интересов для Bob Johnson
db.users.updateOne(
  { name: "Bob Johnson" },
  { $pull: { interests: "gaming" } }
);

Распространенные операторы обновления:

  • $set: Устанавливает значение поля в документе. Создает поле, если оно не существует.
  • $inc: Увеличивает значение поля на указанную величину.
  • $unset: Удаляет поле из документа.
  • $push: Добавляет значение в поле-массив.
  • $pull: Удаляет все экземпляры значения или значений, соответствующих указанному запросу, из массива.
  • $addToSet: Добавляет значение в массив, только если это значение еще не присутствует.

3. db.collection.replaceOne()

Заменяет один документ, соответствующий критериям фильтра, новым документом. Поле _id заменяемого документа изменить нельзя.

// Полностью заменить документ Bob Johnson
db.users.replaceOne(
  { name: "Bob Johnson" },
  {
    name: "Robert Johnson",
    occupation: "Software Engineer",
    status: "active",
    email: "[email protected]"
  }
);

Опция Upsert

Оба метода updateOne() и updateMany() поддерживают опцию upsert. Если установлено значение true и ни один документ не соответствует фильтру, MongoDB вставит новый документ на основе запроса и операций обновления.

db.users.updateOne(
  { name: "David Lee" },
  { $set: { age: 28, city: "Seattle" } },
  { upsert: true } // Если David Lee не существует, создать его
);

Операции удаления

Операции удаления удаляют документы из коллекции. Будьте предельно осторожны с операциями удаления, так как они необратимы.

1. db.collection.deleteOne()

Удаляет не более одного документа, соответствующего указанному фильтру.

// Удалить пользователя с именем 'Robert Johnson' (ранее 'Bob Johnson')
db.users.deleteOne({ name: "Robert Johnson" });

2. db.collection.deleteMany()

Удаляет все документы, соответствующие указанному фильтру.

// Удалить всех пользователей из Лондона
db.users.deleteMany({ city: "London" });

Предупреждение: Чтобы удалить все документы в коллекции, используйте пустой фильтр {}. Будьте предельно осторожны, так как эту операцию нельзя отменить:

db.users.deleteMany({}); // Удаляет все документы в коллекции 'users'

3. db.collection.drop()

Этот метод навсегда удаляет всю коллекцию из базы данных, включая все ее документы и индексы.

db.users.drop(); // Удаляет всю коллекцию 'users'

Предупреждение: Удаление коллекции — это крайне разрушительная операция. Убедитесь, что у вас есть надлежащие резервные копии или вы абсолютно уверены, прежде чем выполнять эту команду.

Лучшие практики для MongoDB CRUD

  • Индексирование: Для часто запрашиваемых полей создавайте индексы, чтобы значительно ускорить операции чтения. db.collection.createIndex({ fieldName: 1 }).
  • Проекции: Извлекайте только те данные, которые вам нужны. Использование проекций ({ field: 1 }) уменьшает использование сетевой пропускной способности и памяти.
  • Пакетные операции: При вставке, обновлении или удалении множества документов используйте insertMany(), updateMany() и deleteMany() вместо отдельных операций, чтобы снизить накладные расходы.
  • Понимание операторов: Ознакомьтесь с богатым набором операторов запросов и обновления MongoDB. Они предоставляют мощные способы манипуляции данными.
  • Обработка ошибок: В производственном приложении всегда реализуйте надежную обработку ошибок для операций с базой данных.
  • Проектирование схемы: Хотя MongoDB не требует схемы, продуманное проектирование схемы имеет решающее значение для эффективных запросов и согласованности данных.

Более безопасный способ практиковать CRUD в реальной базе данных

Опасная часть MongoDB CRUD — это не синтаксис. Это выполнение широкого фильтра в неправильной базе данных, особенно с updateMany() или deleteMany(). Я предпочитаю использовать трехэтапную привычку для любой записи, которая затрагивает существующие данные.

Во-первых, выполните фильтр как чтение:

db.users.find(
  { city: "New York", status: "inactive" },
  { name: 1, email: 1, city: 1, status: 1 }
).limit(20);

Если предварительный просмотр возвращает документы, которые вы не ожидали, остановитесь. Исправьте фильтр, прежде чем думать об обновлении. Если он ничего не возвращает, убедитесь, что вы находитесь в правильной базе данных с помощью db.getName() и что имена полей соответствуют фактическим документам.

Во-вторых, подсчитайте соответствующие документы:

db.users.countDocuments({ city: "New York", status: "inactive" });

Это дает вам приблизительный радиус поражения. Если вы ожидали 12 пользователей, а счет показывает 12 000, команда говорит вам, что что-то не так. Подсчет не заменяет резервную копию, но это дешевое ограждение.

В-третьих, выполните запись с самой узкой командой, которая подходит для задачи. Используйте updateOne(), когда уникальный email, идентификатор учетной записи или _id должны соответствовать одному документу. Используйте updateMany() только тогда, когда изменение целого сегмента является намеренным.

db.users.updateMany(
  { city: "New York", status: "inactive" },
  {
    $set: {
      marketingEmailEnabled: false,
      updatedBy: "ops-maintenance-2025-11-04"
    }
  }
);

Добавление updatedBy, updatedAt или примечания по обслуживанию не требуется MongoDB, но это помогает позже, когда кто-то спросит, почему изменилось поле.

Распространенные ошибки, вызывающие реальные баги

Первая ошибка — замена документа, когда вы хотели обновить поле. Эта команда выглядит безобидно, но она заменяет весь соответствующий документ только показанными полями:

db.users.updateOne(
  { email: "[email protected]" },
  { city: "Boston" }
);

Современная MongoDB ожидает операторы обновления для документов обновления, если только вы не используете методы замены, поэтому этот шаблон может завершиться ошибкой в зависимости от команды и версии. Более безопасная ментальная модель проста: если вы изменяете поля на месте, используйте $set, $unset, $inc, $push, $pull или другой оператор обновления.

db.users.updateOne(
  { email: "[email protected]" },
  { $set: { city: "Boston" } }
);

Вторая ошибка — запрос массивов так, как будто порядок всегда имеет значение. { interests: ["reading", "hiking"] } соответствует массиву точно. { interests: "reading" } соответствует документам, где массив содержит это значение. { interests: { $all: ["reading", "hiking"] } } соответствует массивам, содержащим оба значения, независимо от порядка. Это три разных вопроса.

Третья ошибка — предположение, что findOne() возвращает "правильный" документ, когда фильтр не уникален. Если вы выполните:

db.users.findOne({ city: "New York" });

MongoDB возвращает один соответствующий документ, но без сортировки вы не должны рассматривать этот документ как самый новый, самый старый, самый важный или самый репрезентативный. Если порядок имеет значение, укажите его:

db.users.find({ city: "New York" }).sort({ createdAt: -1 }).limit(1);

Четвертая ошибка — пропуск индексов до тех пор, пока приложение уже не станет медленным. Коллекция с несколькими тысячами документов может скрыть неэффективный запрос. Тот же запрос может стать болезненным, когда коллекция вырастет. Если приложение часто находит пользователей по email, создайте уникальный индекс, когда модель данных это позволяет:

db.users.createIndex({ email: 1 }, { unique: true });

Это защищает как производительность, так и качество данных. Если дубликаты email уже существуют, команда завершится ошибкой, что как раз та проблема, которую вы хотите обнаружить до того, как полагаться на email как на идентификатор.

Проверка того, что на самом деле сделала запись

Результаты записи MongoDB стоит читать. После обновления посмотрите на matchedCount и modifiedCount. matchedCount показывает, сколько документов соответствовало фильтру. modifiedCount показывает, сколько было фактически изменено.

Если matchedCount равен 1, а modifiedCount равен 0, команда все еще может быть в порядке. Возможно, поле уже имело запрошенное значение. Если matchedCount равен 0, ваш фильтр не соответствовал ни одному документу. Это часто случается, когда _id передается как строка вместо ObjectId.

db.users.findOne({ _id: ObjectId("6650f1e59d0a41a37c2d8011") });

Для удалений проверьте deletedCount. Если вы ожидали один удаленный документ, а результат говорит deletedCount: 0, не выполняйте сразу более широкое удаление. Перепроверьте базу данных, коллекцию и фильтр.

Когда CRUD недостаточно

Базовые команды CRUD покрывают большую часть повседневной работы с данными, но некоторые задачи требуют более мощных инструментов. Если вы обновляете несколько коллекций, которые должны оставаться согласованными вместе, посмотрите на транзакции в наборах реплик или шардированных кластерах. Если вы преобразуете данные во многих документах, конвейер агрегации может быть понятнее, чем длинная серия клиентских циклов. Если вы переносите производственные данные, используйте скрипт с ведением журнала, режимом пробного запуска, резервными копиями и планом отката.

Для разовых операций в mongosh делайте команды читаемыми. Хитрый однострочник сложнее проверить и сложнее восстановиться после него. В производстве скучные команды обычно лучше.

Команды MongoDB CRUD просты, как только вы привыкнете к документам, фильтрам и операторам обновления. Навык, который имеет значение в реальной работе, — это осознанность: предварительно просмотрите фильтр, оцените влияние, выберите самую узкую команду записи, прочитайте результат и оставьте достаточно контекста, чтобы следующий человек мог понять, что изменилось.