Раздел 36 · MongoDB с нуля

Вставка: insertOne и insertMany

junior~30 мин

без входаВойди, чтобы отмечать прогресс, вести заметки и решать задачи в редакторе. войти

В прошлом уроке мы осмотрелись в mongosh и положили в базу первый документ. С этого урока начинается кластер про CRUD: создание, чтение, обновление и удаление. Начинаем с первой буквы, со вставки. Заодно у магазина появится полная учебная база, на которой будут работать все следующие уроки.

Папка с анкетами

Представь регистратуру, где на каждого посетителя заводят бумажную анкету и кладут в папку. Анкету можно сдать одну, а можно принести пачку сразу. Бланков нет: у одного посетителя заполнен телефон, у другого ещё и адрес, и регистратура принимает обе.

В MongoDB папка это коллекция, анкета это документ, а сдать анкету значит выполнить вставку. Команд для неё две:

  • insertOne вставляет один документ;
  • insertMany вставляет массив документов за одно обращение к серверу.

insertOne

Добавим в магазин книгу Стругацких:

db.books.insertOne({
  title: 'Понедельник начинается в субботу',
  genre: 'фантастика',
  price: 480,
  pages: 256,
  year: 1965,
  author: { _id: 2, name: 'Стругацкие', country: 'Россия' },
  tags: ['фантастика', 'юмор']
})
{
  acknowledged: true,
  insertedId: ObjectId('6ac9371c7c7353d2ce52946e')
}

Сервер ответил двумя полями. acknowledged: true значит, что он принял запись. insertedId это номер нового документа. Мы его не указывали, поэтому MongoDB придумала его сама: так устроено поле _id, ему посвящён следующий урок. У тебя номер будет другим.

Создавать коллекцию заранее не нужно. Если вставить документ в коллекцию, которой ещё нет, MongoDB создаст её на лету:

db.wishlist.insertOne({ customer_id: 1, book_id: 8, note: 'на день рождения' })
show collections
{
  acknowledged: true,
  insertedId: ObjectId('6ac9372130bd84f0b4184d2f')
}
authors
books
customers
orders
wishlist

У этой свободы есть оборотная сторона: опечатка в имени коллекции тоже молча создаст новую коллекцию. db.bookz.insertOne(...) не упадёт, а положит книгу в bookz.

insertMany

Когда документов много, вставлять их по одному медленно: каждый insertOne это отдельный запрос по сети. insertMany принимает массив и отправляет его разом.

db.books.insertMany([
  { title: 'Чапаев и Пустота', author: { name: 'Пелевин' } },
  { title: 'Лавр', price: 590, audio: true, narrator: 'Александр Клюквин' }
])
{
  acknowledged: true,
  insertedIds: {
    '0': ObjectId('6ac937233570afcc64861c5e'),
    '1': ObjectId('6ac937233570afcc64861c5f')
  }
}

В ответе insertedIds: номер документа в массиве и его _id. Обрати внимание на сами документы. У первого нет ни цены, ни жанра, у второго есть поля audio и narrator, которых нет ни у одной другой книги. MongoDB приняла оба. Это и есть гибкая схема: структуру документа задаёт приложение, а не база. Как всё-таки навести порядок через валидацию, разберём в кластере про моделирование.

Что будет, если один документ не подошёл

У каждого документа в коллекции свой _id, двух одинаковых быть не может. Попробуем вставить трёх авторов, один из которых случайно получил номер Булгакова:

db.authors.insertMany([
  { _id: 7, name: 'Пелевин', country: 'Россия' },
  { _id: 1, name: 'Ещё один Булгаков', country: 'Россия' },
  { _id: 8, name: 'Улицкая', country: 'Россия' }
])
db.authors.find({ _id: { $gte: 7 } })
Uncaught:
MongoBulkWriteError: E11000 duplicate key error collection: test.authors index: _id_ dup key: { _id: 1 }
Result: BulkWriteResult {
  insertedCount: 1,
  matchedCount: 0,
  modifiedCount: 0,
  deletedCount: 0,
  upsertedCount: 0,
  upsertedIds: {},
  insertedIds: { '0': 7 }
}
Write Errors: [
  WriteError {
    err: {
      index: 1,
      code: 11000,
      errmsg: 'E11000 duplicate key error collection: test.authors index: _id_ dup key: { _id: 1 }',
      errInfo: undefined,
      op: { _id: 1, name: 'Ещё один Булгаков', country: 'Россия' }
    }
  }
]
[ { _id: 7, name: 'Пелевин', country: 'Россия' } ]

Код E11000 значит «такой ключ уже есть». Важнее другое: Пелевин вставился, а Улицкая нет. По умолчанию insertMany работает в режиме упорядоченной вставки: идёт по массиву сверху вниз и останавливается на первой ошибке. Всё, что до ошибки, остаётся в коллекции, всё, что после, не вставляется.

Это не транзакция. В PostgreSQL INSERT с тремя строками и одной ошибкой не вставил бы ни одной, а здесь первый документ уже лежит в базе. Как в MongoDB сделать «всё или ничего», расскажем в кластере про транзакции.

Если порядок не важен, можно попросить MongoDB вставить всё, что получится:

db.authors.insertMany(
  [
    { _id: 7, name: 'Пелевин', country: 'Россия' },
    { _id: 1, name: 'Ещё один Булгаков', country: 'Россия' },
    { _id: 8, name: 'Улицкая', country: 'Россия' }
  ],
  { ordered: false }
)
db.authors.find({ _id: { $gte: 7 } })

Ошибка будет та же, только в insertedIds окажутся документы с номерами 0 и 2. А find покажет обоих новых авторов:

[
  { _id: 7, name: 'Пелевин', country: 'Россия' },
  { _id: 8, name: 'Улицкая', country: 'Россия' }
]

С ordered: false MongoDB пропускает документы с ошибками и вставляет остальные. На больших загрузках так ещё и быстрее: сервер вправе менять порядок операций ради скорости.

урок из курса

Урок из курса «MongoDB с нуля». Курс покупается один раз и остаётся навсегда: 500 ₽.

Оплата пока не подключена: премиум и курсы открываются промокодом.