ITcompressor IT — сжато и по делу
← Назад к статьям

MongoDB

MongoDB — документоориентированная СУБД: данные хранятся не в таблицах, а в коллекциях JSON-подобных документов (BSON). Гибкая схема, богатый язык запросов и встроенная репликация.

Установка#

Официальный репозиторий для Ubuntu 22.04 (MongoDB 7.0):

# Ключ подписи репозитория (apt-key устарел — кладём ключ в keyring)
sudo apt install gnupg curl
curl -fsSL https://www.mongodb.org/static/pgp/server-7.0.asc | \
  sudo gpg -o /usr/share/keyrings/mongodb-server-7.0.gpg --dearmor

# Репозиторий
echo "deb [ arch=amd64,arm64 signed-by=/usr/share/keyrings/mongodb-server-7.0.gpg ] https://repo.mongodb.org/apt/ubuntu jammy/mongodb-org/7.0 multiverse" | sudo tee /etc/apt/sources.list.d/mongodb-org-7.0.list

# Установка и запуск
sudo apt update
sudo apt install -y mongodb-org
sudo systemctl enable --now mongod

Подключение к шеллу (в современных версиях это mongosh, legacy-клиент mongo удалён):

mongosh

Всё ниже — команды шелла (JavaScript). Демонстрируем на коллекции customers с документами вида { name, email, city, age, status }.

Вставка#

// Один документ
db.customers.insertOne({
  name: "Olga Smirnova",
  email: "olga@example.com",
  city: "Minsk",
  age: 29,
  status: "active"
})

// Массив документов — вставка пачкой
db.customers.insertMany([
  { name: "Ivan Petrov",  email: "ivan@example.com", city: "Gomel", age: 34, status: "inactive" },
  { name: "Anna Koval",   email: "anna@example.com", city: "Minsk", age: 41, status: "active" },
  { name: "Maksim Lee",   email: "maks@example.com", city: "Brest", age: 23, status: "active" }
])

Коллекция создаётся автоматически при первой вставке; у документа без явного _id MongoDB сгенерирует его сама.

Выборки#

Простые#

// Все документы коллекции
db.customers.find()

// Один документ, совпавший первым
db.customers.findOne({ name: "Anna Koval" })

// По точному значению поля
db.customers.find({ city: "Minsk" })

Операторы сравнения#

// Возраст больше 25
db.customers.find({ age: { $gt: 25 } })

// Комбинированный диапазон: 25 <= age <= 40
db.customers.find({ age: { $gte: 25, $lte: 40 } })

// Вхождение в набор значений
db.customers.find({ city: { $in: ["Minsk", "Brest"] } })

// Поле существует / отсутствует
db.customers.find({ phone: { $exists: true } })

// Подстрока в начале поля (регулярное выражение)
db.customers.find({ name: { $regex: "^A" } })

Основные операторы сравнения: $eq, $ne, $gt, $gte, $lt, $lte, $in, $nin, $exists, $regex.

Логические операторы#

// И: оба условия одновременно
db.customers.find({
  age: { $gte: 25 },
  status: "active"
})

// Явная форма с $and (нужна, когда условие на одно поле сложное)
db.customers.find({
  $and: [
    { age: { $gte: 25 } },
    { age: { $lte: 40 } }
  ]
})

// ИЛИ: хотя бы одно из условий
db.customers.find({
  $or: [
    { city: "Minsk" },
    { age: { $gte: 35 } }
  ]
})

// Отрицание набора условий
db.customers.find({ $nor: [ { status: "inactive" }, { age: { $lt: 25 } } ] })

Проекции, сортировка и пагинация#

// Только name и email (без _id) у активных клиентов
db.customers.find(
  { status: "active" },
  { name: 1, email: 1, _id: 0 }
)

// Свежие первыми, 10 документов, третья страница
db.customers.find()
  .sort({ age: -1 })
  .limit(10)
  .skip(20)

Агрегации#

Конвейер документов: каждый этап ($match, $group, $sort, …) обрабатывает поток и передаёт результат следующему.

// Группировка по городу: количество и средний возраст
db.customers.aggregate([
  { $match: { status: "active" } },
  { $group: { _id: "$city", count: { $sum: 1 }, avgAge: { $avg: "$age" } } },
  { $sort: { count: -1 } },
  { $limit: 5 }
])

Обновления#

// Обновить один документ: заменить поля через $set
db.customers.updateOne(
  { name: "Ivan Petrov" },
  { $set: { status: "active" } }
)

// Обновить все документы под фильтр
db.customers.updateMany(
  { status: "inactive" },
  { $set: { city: "unknown" } }
)

// Инкремент счётчика
db.customers.updateOne(
  { name: "Anna Koval" },
  { $inc: { logins: 1 } }
)

// Добавить элемент в массив
db.customers.updateOne(
  { name: "Anna Koval" },
  { $push: { tags: "vip" } }
)

// Удалить поле целиком
db.customers.updateOne(
  { name: "Anna Koval" },
  { $unset: { phone: "" } }
)

Без $set и других операторов updateOne({ … }, { age: 30 }) заменит весь документ на новый. Всегда указывайте оператор.

Довольно частый паттерн — апsert: обновить, а если документа нет, вставить (пригоден для инкрементальных синхронизаций):

db.customers.updateOne(
  { email: "new@example.com" },
  { $set: { name: "New User", status: "active" } },
  { upsert: true }
)

Удаления#

// Один документ (первый совпавший)
db.customers.deleteOne({ email: "maks@example.com" })

// Все документы под фильтр
db.customers.deleteMany({ status: "inactive" })

// Удалить коллекцию вместе с индексами
db.customers.drop()

deleteMany({}) чистит документы, drop() — коллекцию целиком (включая индексы). Для «зачистки таблицы» быстрее именно drop().

Реплика-сет#

Репликация держит копии данных на нескольких узлах: записи принимает один PRIMARY, остальные синхронизируются с ним по oplog. При падении PRIMARY участники выбирают нового — кластер продолжает работать.

Конфигурация узлов#

Одинаковый replSetName в /etc/mongod.conf на каждом узле. Обратите внимание: bindIp и port живут в секции net, а не в replication:

# /etc/mongod.conf
net:
  bindIp: 0.0.0.0
  port: 27017
replication:
  replSetName: rs0

Затем на каждом узле: sudo systemctl restart mongod. Узлам нужен доступ друг к другу и клиентам по порту 27017:

# Точечно: только из доверенной подсети
sudo ufw allow from 10.0.0.0/24 to any port 27017

Инициализация#

Подключаемся к одному узлу и инициализируем набор — остальные перечислены в members:

// Подключение (реплика-сет указывается в строке подключения)
mongosh "mongodb://mongo1.example.com:27017/?replicaSet=rs0"
// Выполняется один раз, на любом перечисленном узле
rs.initiate({
  _id: "rs0",
  members: [
    { _id: 0, host: "mongo1.example.com:27017" },
    { _id: 1, host: "mongo2.example.com:27017" },
    { _id: 2, host: "mongo3.example.com:27017" }
  ]
})

Статус и конфигурация#

// Состояние участников: health, state (PRIMARY/SECONDARY), lag
rs.status()

// Текущая конфигурация набора
rs.conf()

// Передать роль PRIMARY другому узлу (например, перед обслуживанием)
rs.stepDown()

Добавление и удаление участников#

// Подключить новый узел (сначала — с тем же replSetName в mongod.conf)
rs.add("mongo4.example.com:27017")

// Убрать узел из набора
rs.remove("mongo3.example.com:27017")

Приоритет участника задаётся в конфиге набора: rs.conf().members — поле priority; узел с наибольшим приоритетом выигрывает выборы PRIMARY. Для реплика-сета нужны минимум три узла с данным (или арбитры) — кворум должен быть надёжным.

← Назад к статьям