MongoDB — документо-ориентированная база данных
MongoDB — самая популярная NoSQL база данных в мире. Она хранит данные в формате документов (JSON/BSON), не требует жёсткой схемы и масштабируется горизонтально.
1. Что такое MongoDB
MongoDB — это документо-ориентированная NoSQL база данных. Вместо строк и столбцов она хранит данные в виде документов в формате BSON (бинарный JSON).
Сравнение с реляционными БД
| Концепция | SQL | MongoDB |
|---|---|---|
| Хранилище данных | Таблица | Коллекция |
| Единица данных | Строка (row) | Документ (document) |
| Схема | Жёсткая (ALTER TABLE) | Гибкая (каждый документ может отличаться) |
| Язык запросов | SQL | MongoDB Query API |
| Связи | JOIN | Вложенность или $lookup |
Пример документа:
{
"_id": ObjectId("64f1a2b3c4d5e6f7a8b9c0d1"),
"name": "Иван Петров",
"age": 28,
"email": "ivan@example.com",
"tags": ["python", "mongodb"],
"address": {
"city": "Москва",
"street": "Ленина 10"
}
}Когда использовать MongoDB
- Структура данных неизвестна заранее или часто меняется.
- Нужна горизонтальная масштабируемость (шардинг).
- Высокая скорость разработки — нет миграций схемы.
- Данные логически группируются (каталог товаров, логи, сессии).
2. Установка и запуск
Скачай MongoDB Community Server с mongodb.com и установи. По умолчанию сервер слушает порт 27017.
Запуск сервера
mongod --dbpath /data/dbВ отдельном терминале подключись через клиент mongosh:
mongoshПроверь подключение:
db.runCommand({ ping: 1 })
{ ok: 1 }GUI — MongoDB Compass
Скачай Compass с mongodb.com/products/compass. Подключись по адресу mongodb://localhost:27017.
Запуск через Docker
docker run -d --name mongo -p 27017:27017 mongo:7-v mongo-data:/data/db.3. Основные понятия
| Термин | Описание |
|---|---|
| База данных | Контейнер для коллекций. Создаётся автоматически при первом обращении. |
| Коллекция | Группа документов — аналог таблицы в SQL. |
| Документ | Запись в коллекции. Формат BSON (JSON-подобный). |
| _id | Уникальный идентификатор документа. Тип ObjectId по умолчанию. |
ObjectId — это 12-байтный ключ: 4 байта временной метки + 5 байт рандома + 3 байта счётчика. Он гарантирует уникальность без централизованного генератора.
ObjectId("64f1a2b3c4d5e6f7a8b9c0d1")
↑ 4 байта ↑ 5 байт ↑ 3 байта
Timestamp Random CounterДокументы могут содержать вложенность:
{
"title": "iPhone 15",
"price": 89990,
"specs": {
"screen": "6.1-inch",
"chip": "A17 Pro"
},
"colors": ["black", "white", "blue"]
}4. Вставка документов
insertOne
db.users.insertOne({
name: "Иван Петров",
age: 28,
email: "ivan@example.com",
orders: [
{ product: "Ноутбук", price: 75000, date: ISODate("2025-01-15") },
{ product: "Мышь", price: 1500, date: ISODate("2025-02-20") }
]
})Ответ:
{
acknowledged: true,
insertedId: ObjectId("64f1a2b3c4d5e6f7a8b9c0d1")
}insertMany
db.users.insertMany([
{
name: "Мария Сидорова",
age: 34,
email: "maria@example.com",
tags: ["admin", "editor"]
},
{
name: "Алексей Козлов",
age: 22,
email: "alex@example.com",
tags: ["viewer"]
}
])5. Запросы — find
Получение всех документов
db.users.find()Фильтрация
db.users.find({ age: { $gt: 30 } })
db.users.find({ "tags": "admin" })
db.users.find({ age: { $gte: 25, $lte: 35 } })Логические операторы
db.users.find({
$or: [
{ age: { $gt: 30 } },
{ tags: "admin" }
]
})
db.users.find({
$and: [
{ age: { $gte: 20 } },
{ tags: "viewer" }
]
})Проекция
Второй аргумент find — проекция. Укажи 1 для включения или 0 для исключения:
db.users.find(
{ age: { $gt: 25 } },
{ name: 1, email: 1, _id: 0 }
)Форматированный вывод
db.users.find().pretty()6. Обновление документов
updateOne и updateMany
db.users.updateOne(
{ name: "Иван Петров" },
{ $set: { age: 29, "address.city": "Санкт-Петербург" } }
)
db.users.updateMany(
{ tags: "viewer" },
{ $inc: { age: 1 } }
)Другие операторы обновления
| Оператор | Описание | Пример |
|---|---|---|
| $set | Установить значение | { $set: { status: "active" } } |
| $inc | Увеличить на число | { $inc: { views: 1 } } |
| $push | Добавить в массив | { $push: { tags: "new" } } |
| $pull | Убрать из массива | { $pull: { tags: "old" } } |
| $unset | Удалить поле | { $unset: "temp" } |
findOneAndUpdate
db.users.findOneAndUpdate(
{ name: "Иван Петров" },
{ $set: { lastLogin: ISODate() } },
{ returnDocument: "after" }
)Upsert
Параметр upsert: true создаёт документ, если он не найден:
db.users.updateOne(
{ email: "new@example.com" },
{ $set: { name: "Новый", age: 25 } },
{ upsert: true }
)7. Удаление документов
deleteOne
db.users.deleteOne({ name: "Алексей Козлов" })deleteMany
db.users.deleteMany({ age: { $lt: 18 } })Очистка всей коллекции
db.users.deleteMany({})
db.users.drop()8. Агрегация
Aggregation Pipeline — цепочка этапов обработки данных. Каждый этап получает результат предыдущего.
Основные этапы
| Этап | Описание |
|---|---|
| $match | Фильтрация документов (аналог find) |
| $group | Группировка по полю с агрегатными функциями |
| $sort | Сортировка результатов |
| $limit | Ограничение количества документов |
| $project | Выбор и переименование полей |
Пример: средний возраст по городам
db.users.aggregate([
{ $match: { age: { $gte: 18 } } },
{
$group: {
_id: "$address.city",
avgAge: { $avg: "$age" },
totalUsers: { $sum: 1 }
}
},
{ $sort: { avgAge: -1 } }
])Результат:
[
{ _id: "Москва", avgAge: 31, totalUsers: 150 },
{ _id: "Санкт-Петербург", avgAge: 29, totalUsers: 80 }
]Пример: топ-5 самых дорогих заказов
db.orders.aggregate([
{ $unwind: "$items" },
{ $group: {
_id: "$userId",
totalSpent: { $sum: "$items.price" }
}
},
{ $sort: { totalSpent: -1 } },
{ $limit: 5 }
])9. Индексы и производительность
Индексы ускоряют поиск. Без индекса MongoDB выполняет коллекционное сканирование (full collection scan).
Создание индекса
db.users.createIndex({ email: 1 }, { unique: true })
db.users.createIndex({ age: 1, "address.city": 1 })Значение 1 — восходящий индекс, -1 — нисходящий.
Проверка выполнения запроса
db.users.find({ email: "ivan@example.com" }).explain("executionStats")В выводе обрати внимание на totalDocsExamined — с индексом это число мало, без индекса равно размеру коллекции.
TTL-индексы
TTL-индексы автоматически удаляют документы через указанное время. Идеальны для кэша и сессий:
db.sessions.createIndex(
{ createdAt: 1 },
{ expireAfterSeconds: 3600 }
)Документы с полем createdAt старше 1 часа будут удалены автоматически.
10. Работа из Python
Установка pymongo
pip install pymongoПодключение и CRUD
from pymongo import MongoClient
client = MongoClient("mongodb://localhost:27017")
db = client["myshop"]
users = db["users"]
users.insert_one({
"name": "Иван",
"age": 28,
"email": "ivan@example.com"
})
user = users.find_one({"name": "Иван"})
print(user["email"])
for u in users.find({"age": {"$gte": 25}}):
print(u["name"], u["age"])
users.update_one({"name": "Иван"}, {"$set": {"age": 29}})
users.delete_one({"name": "Иван"})user["name"] или user.get("name").Motor — асинхронный клиент
pip install motorimport asyncio
from motor.motor_asyncio import AsyncIOMotorClient
client = AsyncIOMotorClient("mongodb://localhost:27017")
db = client["myshop"]
async def main():
await db.users.insert_one({"name": "Пётр", "age": 30})
user = await db.users.find_one({"name": "Пётр"})
print(user)
asyncio.run(main())11. MongoDB Atlas и продакшен
MongoDB Atlas — облачная управляемая версия MongoDB. Бесплатный тариф включает 512 МБ хранилища.
Подключение из приложения
from pymongo import MongoClient
client = MongoClient(
"mongodb+srv://user:password@cluster0.abc123.mongodb.net/"
"?retryWrites=true&w=majority"
)
db = client["production"]Бэкапы и безопасность
- Atlas автоматически делает снимки кластера каждые 6 часов.
- Настройте IP-whitelist в настройках кластера.
- Используйте роль
readWriteдля приложений, неroot. - Включите шифрование данных (encryption at rest).
Вставка в REST-приложение
from fastapi import FastAPI
from pymongo import MongoClient
app = FastAPI()
client = MongoClient("mongodb://localhost:27017")
db = client["api"]
@app.post("/users")
def create_user(name: str, age: int):
result = db.users.insert_one({"name": name, "age": age})
return {"id": str(result.inserted_id)}
@app.get("/users/{user_id}")
def get_user(user_id: str):
from bson import ObjectId
user = db.users.find_one({"_id": ObjectId(user_id)})
if user:
user["_id"] = str(user["_id"])
return user
return {"error": "not found"}12. Транзакции и ACID
В отличие от ранних версий, современный MongoDB (начиная с 4.0) поддерживает многодокументные транзакции с гарантиями ACID. Транзакция работает только на реплика-сетах и позволяет гарантировать согласованность нескольких записей одновременно.
const session = client.startSession();
session.startTransaction();
try {
await db.orders.insertOne(order, { session });
await db.accounts.updateOne(
{ _id: userId },
{ $inc: { balance: -order.total } },
{ session }
);
await session.commitTransaction();
} catch (err) {
await session.abortTransaction();
throw err;
} finally {
session.endSession();
}Таймаут транзакции по умолчанию — 60 секунд (transactionLifetimeLimitSeconds). Держите транзакции короткими: читайте данные до транзакции, а внутри меняйте только то, что критично для атомарности.
13. Репликация и отказоустойчивость
Реплика-сет (replica set) — это группа серверов MongoDB, хранящих одинаковый набор данных. Один узел — primary (принимает записи), остальные — secondary (копии данных). Если primary выходит из строя, набор автоматически выбирает нового primary за несколько секунд.
- Автоматическое восстановление — выборы (election) нового primary без ручных действий.
- Чтение с вторичных узлов — распределение нагрузки через read preference.
- Свежие копии — вторичные узлы хранят актуальные данные, поэтому потеря записи минимальна.
rs.initiate({
_id: "rs0",
members: [
{ _id: 0, host: "10.0.0.1:27017" },
{ _id: 1, host: "10.0.0.2:27017" },
{ _id: 2, host: "10.0.0.3:27017" }
]
})14. Роли и безопасность доступа
По умолчанию MongoDB слушает localhost без пароля. Для продакшена обязательно включайте авторизацию (--auth или security.authorization) и создавайте отдельного пользователя на каждое приложение с минимумом прав.
| Роль | Права |
|---|---|
| read | чтение документов из коллекций базы |
| readWrite | чтение, вставка, обновление и удаление |
| dbAdmin | администрирование: индексы, валидация |
| userAdmin | управление пользователями базы |
| clusterAdmin | полный доступ к кластеру и репликации |
use admin
db.createUser({
user: "app_user",
pwd: passwordPrompt(),
roles: ["readWrite"]
})15. Бэкапы и восстановление
Утилита mongodump сохраняет данные в BSON-файлы, а mongorestore восстанавливает их. Для горячих копий реплика-сета предпочтителен непрерывный бэкап через Ops Manager или Atlas — там снимки делаются автоматически.
mongodump --uri="mongodb://localhost:27017" \
--db=shop --out=/backups/shop_2026-09-19
mongorestore --uri="mongodb://localhost:27017" \
--db=shop /backups/shop_2026-09-19/shopРегулярно проверяйте восстановление: бэкап, который никогда не пробовали развернуть, — это просто файлы на диске. Расписание: cron раз в сутки плюс еженедельная проверка целостности.
16. Change Streams: реакция на изменения
Change Streams дают приложению подписку на изменения в коллекции, базе или кластере: вставки, обновления и удаления приходят в режиме реального времени. Это аналог SQL-триггеров, но на стороне сервиса — реакция на события остаётся в коде, а не в базе.
const pipeline = [
{ $match: { operationType: 'insert' } }
];
const stream = db.catalog.watch(pipeline);
stream.on('change', (change) => {
const doc = change.fullDocument;
console.log('Новый товар:', doc._id);
});Поток возобновим: сохраняйте поле resumeToken события и при перезапуске продолжайте чтение с последней позиции. Потери событий при этом исключены — в отличие от периодических опросов коллекции.
| Сценарий | Пример |
|---|---|
| Единый стриминг | синхронизация данных в поисковую систему |
| Событийная архитектура | уведомления, планировщики, аналитика |
| Материализованные представления | поддержание агрегатов на лету |
17. Геопространственные запросы и GeoJSON
MongoDB умеет хранить геометрию в формате GeoJSON (точки, линии, полигоны) и отвечать на вопросы «что рядом», «что внутри района», «какие маршруты пересекаются». Для скорости создайте индекс 2dsphere по геополю.
db.places.createIndex({ location: '2dsphere' });
db.places.find({
location: {
$near: {
$geometry: { type: 'Point', coordinates: [37.62, 55.75] },
$maxDistance: 1000
}
}
});| Оператор | Назначение |
|---|---|
| $near | объекты рядом, отсортированные по расстоянию |
| $geoWithin | объекты внутри полигона или круга |
| $geoIntersects | геометрии, пересекающие заданную область |
18. Полнотекстовый поиск и Atlas Search
Встроенный текстовый индекс text позволяет искать по словам и фразам, учитывая язык и стоп-слова. Достаточно создать его по одному или нескольким полям, а запрос написать через $text.
db.articles.createIndex(
{ title: 'text', body: 'text' },
{ default_language: 'russian' }
);
db.articles.find(
{ $text: { $search: 'питон базы данных' } },
{ score: { $meta: 'textScore' } }
).sort({ score: { $meta: 'textScore' } });Оператор $meta: 'textScore' добавляет в документ релевантность совпадения, по которой сортируются результаты. Фразы заключайте в кавычки, а минус перед словом исключает его из поиска.
19. Шардинг: горизонтальное масштабирование
Шардинг распределяет данные между несколькими реплика-сетами по ключу шардирования. Запросы идут через маршрутизатор mongos, который знает, где хранится каждый фрагмент данных. Это даёт горизонтальное масштабирование, когда один сервер уже не справляется.
sh.enableSharding('catalog');
sh.shardCollection(
'catalog.products',
{ category: 1, _id: 1 }
);| Стратегия | Поведение | Применение |
|---|---|---|
| hashed | равномерное распределение по хэшу ключа | записи без диапазонных запросов |
| ranged | диапазоны значений (даты, числа) | отчёты и срезы по интервалу |
20. Валидация схем: JSON Schema
В отличие от SQL, MongoDB по умолчанию не проверяет структуру документа — новое поле может появиться в любой момент. На продакшене это превращает «небрежность» в тихую катастрофу аналитики, поэтому включите валидацию: документу, не прошедшему правила, вставка будет запрещена или заменена.
db.runCommand({
collMod: 'users',
validator: {
$jsonSchema: {
bsonType: 'object',
required: ['email', 'role'],
properties: {
email: { bsonType: 'string',
pattern: '@' },
role: { enum: ['admin', 'user'] }
}
}
},
validationLevel: 'strict'
});| Уровень | Поведение |
|---|---|
| strict | проверяет новые и обновляемые документы |
| moderate | проверяет только новые вставки |
| off | валидация отключена |
21. TTL-индексы: данные с истечением срока
TTL-индекс по полю с датой автоматически удаляет документы, чьё время вышло: сессии, логи и коды подтверждения не копятся на диске, а чистятся фоновым процессом примерно раз в минуту. Избавьтесь от cron-джобов, удаляющих строки вручную, — база справится сама.
db.sessions.createIndex(
{ createdAt: 1 },
{ expireAfterSeconds: 3600 }
);- Поле обязано быть датой или массивом дат — число миллисекунд не сработает.
- TTL нельзя совмещать с составным индексом: только одно поле.
- Удаление идёт партиями и зависит от загрузки сервера.
22. Курсоры и пагинация без пропусков
Метод find возвращает не все документы сразу, а курсор — окно с ленивой подгрузкой партий. Привычные limit/skip на больших страницах тормозят: серверу приходится заново проходить предыдущие документы. Надёжнее курсор по ключу: следующий запрос начинается с _id последней записи.
let lastId = null;
for (let page = 0; page < 20; page++) {
const q = lastId ? { _id: { $gt: lastId } } : {};
const batch = await db.posts.find(q)
.sort({ _id: 1 }).limit(100).toArray();
if (batch.length === 0) break;
lastId = batch[batch.length - 1]._id;
}Такой подход стабилен, даже если во время листания добавляются новые записи: страницы не «плавают». Чтобы курсор не сканировал весь диск, поле сортировки должно совпадать с индексом — как правило, хватает _id.
23. Паттерн Outbox: события без потерь
Когда MongoDB — источник правды, а изменения должна увидеть поисковая система или очередь, наивное «обновил базу, потом отправил событие» теряет события при падении между двумя шагами. Паттерн outbox кладёт сам документ и запись о событии в одну транзакцию: изменятся либо оба, либо ни один.
const session = db.startSession();
session.startTransaction();
await db.orders.insertOne(order, { session });
await db.outbox.insertOne({
type: 'order.created',
payload: order._id,
status: 'pending'
}, { session });
await session.commitTransaction();Фоновый релейер читает pending-записи, публикует их в RabbitMQ, Kafka или пуш-сервис и помечает обработанными. Получатель обязан быть идемпотентным: повторная доставка не должна задваивать заказы — учитывайте идентификатор события.
Что дальше
Теперь ты знаешь основы MongoDB. Рекомендуемый следующий шаг — изучение Redis для кэширования, или углубление в продвинутые темы: Docker для контейнеризации, FastAPI для построения REST API с MongoDB.