9. MongoDB: Основы
MongoDB — это документо-ориентированная NoSQL база данных, хранящая данные в формате BSON (Binary JSON). Гибкая схема, горизонтальное масштабирование и удобная работа с JSON делают её популярной для современных приложений.
Почему MongoDB?
Заголовок раздела «Почему MongoDB?»graph TD A[MongoDB Features] --> B[Flexible Schema] A --> C[Horizontal Scaling] A --> D[Rich Query Language] A --> E[Aggregation Pipeline]
B --> B1[Документы разной структуры] C --> C1[Sharding из коробки] D --> D1[Мощные запросы] E --> E1[Аналитика в БД]Преимущества:
- Гибкая схема (нет фиксированных колонок)
- Нативная работа с JSON
- Горизонтальное масштабирование (sharding)
- Мощный aggregation framework
- Реплицирование для высокой доступности
Недостатки:
- Нет JOIN’ов (денормализация)
- Больше места на диске (дублирование данных)
- Eventual consistency при репликации
- Сложнее обеспечить строгую консистентность
Основные концепции
Заголовок раздела «Основные концепции»| SQL | MongoDB |
|---|---|
| Database | Database |
| Table | Collection |
| Row | Document |
| Column | Field |
| Index | Index |
| JOIN | Embedding / $lookup |
| Primary Key | _id (автоматически) |
Установка и подключение
Заголовок раздела «Установка и подключение»Локальная установка
Заголовок раздела «Локальная установка»# macOS (Homebrew)brew tap mongodb/brew
# Ubuntuwget -qO - https://www.mongodb.org/static/pgp/server-7.0.asc | sudo apt-key add -echo "deb [ arch=amd64,arm64 ] https://repo.mongodb.org/apt/ubuntu jammy/mongodb-org/7.0 multiverse" | sudo tee /etc/apt/sources.list.d/mongodb-org-7.0.listsudo apt updatesudo apt install -y mongodb-orgsudo systemctl start mongod
# Проверкаmongosh --versionMongoDB Atlas (облако)
Заголовок раздела «MongoDB Atlas (облако)»# Подключение к Atlasmongosh "mongodb+srv://cluster0.xxxxx.mongodb.net/mydb" --apiVersion 1 --username myuserCRUD операции
Заголовок раздела «CRUD операции»Create - Вставка документов
Заголовок раздела «Create - Вставка документов»// Подключениеuse myapp
// Вставка одного документаdb.users.insertOne({ username: "john_doe", age: 30, status: "active", roles: ["user", "moderator"], profile: { firstName: "John", lastName: "Doe", bio: "Software developer" }, createdAt: new Date()})
// Вставка несколькихdb.users.insertMany([ { username: "alice", age: 25, status: "active", roles: ["user"], createdAt: new Date() }, { username: "bob", age: 35, status: "inactive", roles: ["user", "admin"], createdAt: new Date() }])Read - Запросы
Заголовок раздела «Read - Запросы»// Найти всех пользователейdb.users.find()
// Найти с условиемdb.users.find({ status: "active" })
// Найти одногоdb.users.findOne({ username: "john_doe" })
// Проекция (выбор полей)db.users.find( { status: "active" }, { username: 1, email: 1, _id: 0 } // 1 = включить, 0 = исключить)
// Операторы сравненияdb.users.find({ age: { $gt: 25, $lte: 35 } }) // 25 < age <= 35db.users.find({ status: { $in: ["active", "pending"] } })db.users.find({ status: { $ne: "banned" } })
// Логические операторыdb.users.find({ $and: [ { age: { $gte: 18 } }, { status: "active" } ]})
db.users.find({ $or: [ { status: "active" }, { roles: "admin" } ]})
// Вложенные поляdb.users.find({ "profile.firstName": "John" })
// Массивыdb.users.find({ roles: "admin" }) // содержит "admin"db.users.find({ roles: { $all: ["user", "admin"] } }) // содержит обаdb.users.find({ "roles.0": "user" }) // первый элемент = "user"
// Сортировка и ограничениеdb.users.find().sort({ age: -1 }).limit(10).skip(5)
// Подсчётdb.users.countDocuments({ status: "active" })Update - Обновление
Заголовок раздела «Update - Обновление»// Обновить одногоdb.users.updateOne( { username: "john_doe" }, { $set: { age: 31, "profile.bio": "Senior developer" } })
// Обновить многихdb.users.updateMany( { status: "inactive" }, { $set: { status: "archived", archivedAt: new Date() } })
// Операторы обновленияdb.users.updateOne( { username: "alice" }, { $inc: { age: 1 }, // Инкремент $push: { roles: "premium" }, // Добавить в массив $currentDate: { lastModified: true } // Текущая дата })
// Удалить полеdb.users.updateOne( { username: "bob" }, { $unset: { temporaryField: "" } })
// Удалить из массиваdb.users.updateOne( { username: "john_doe" }, { $pull: { roles: "moderator" } })
// Upsert (insert if not exists)db.users.updateOne( { username: "new_user" }, { upsert: true })
// Replace целого документаdb.users.replaceOne( { username: "old_user" },)Delete - Удаление
Заголовок раздела «Delete - Удаление»// Удалить одногоdb.users.deleteOne({ username: "john_doe" })
// Удалить многихdb.users.deleteMany({ status: "archived" })
// Удалить все документыdb.users.deleteMany({})
// Удалить коллекциюdb.users.drop()Индексы
Заголовок раздела «Индексы»// Создание индексаdb.users.createIndex({ email: 1 }) // 1 = ascending, -1 = descending
// Составной индексdb.users.createIndex({ status: 1, createdAt: -1 })
// Уникальный индексdb.users.createIndex({ username: 1 }, { unique: true })
// Частичный индексdb.users.createIndex( { email: 1 }, { partialFilterExpression: { status: "active" } })
// TTL индекс (автоудаление старых документов)db.sessions.createIndex( { createdAt: 1 }, { expireAfterSeconds: 3600 } // удалить через 1 час)
// Текстовый индексdb.articles.createIndex({ title: "text", content: "text" })
// Список индексовdb.users.getIndexes()
// Удалить индексdb.users.dropIndex("email_1")
// Анализ использования индексаTypeScript с Mongoose
Заголовок раздела «TypeScript с Mongoose»import mongoose from 'mongoose';
// Подключениеawait mongoose.connect('mongodb://localhost:27017/myapp');
// Схемаconst userSchema = new mongoose.Schema({ username: { type: String, required: true, unique: true, lowercase: true, trim: true }, email: { type: String, required: true, unique: true, match: /^[^\s@]+@[^\s@]+\.[^\s@]+$/ }, age: { type: Number, min: 18, max: 120 }, status: { type: String, enum: ['active', 'inactive', 'banned'], default: 'active' }, roles: [String], profile: { firstName: String, lastName: String, bio: String }, createdAt: { type: Date, default: Date.now }, updatedAt: { type: Date, default: Date.now }}, { timestamps: true // автоматические createdAt/updatedAt});
// ИндексыuserSchema.index({ email: 1 }, { unique: true });userSchema.index({ status: 1, createdAt: -1 });
// Виртуальные поляuserSchema.virtual('fullName').get(function() { return `${this.profile.firstName} ${this.profile.lastName}`;});
// Методы экземпляраuserSchema.methods.isAdmin = function() { return this.roles.includes('admin');};
// Статические методыuserSchema.statics.findActive = function() { return this.find({ status: 'active' });};
// Middleware (hooks)userSchema.pre('save', function(next) { this.updatedAt = new Date(); next();});
// Модельconst User = mongoose.model('User', userSchema);
// CRUD с Mongooseasync function examples() { // Create const user = await User.create({ username: 'john_doe', age: 30, roles: ['user'], profile: { firstName: 'John', lastName: 'Doe' } });
// Read const users = await User.find({ status: 'active' }) .select('username email') .sort({ createdAt: -1 }) .limit(10);
const byId = await User.findById('507f1f77bcf86cd799439011');
// Update await User.updateOne( { username: 'john_doe' }, { $set: { age: 31 } } );
const updated = await User.findByIdAndUpdate( user._id, { $push: { roles: 'premium' } }, { new: true } // вернуть обновлённый документ );
// Delete await User.deleteOne({ username: 'john_doe' }); await User.findByIdAndDelete(user._id);
// Использование методов const activeUsers = await User.findActive(); console.log(user.fullName); // виртуальное поле console.log(user.isAdmin()); // метод экземпляра}TypeScript с нативным драйвером
Заголовок раздела «TypeScript с нативным драйвером»import { MongoClient, ObjectId } from 'mongodb';
const client = new MongoClient('mongodb://localhost:27017');
interface User { _id?: ObjectId; username: string; email: string; age: number; status: 'active' | 'inactive' | 'banned'; roles: string[]; createdAt: Date;}
async function main() { await client.connect();
const db = client.db('myapp'); const users = db.collection<User>('users');
// Create const result = await users.insertOne({ username: 'john_doe', age: 30, status: 'active', roles: ['user'], createdAt: new Date() }); console.log('Inserted:', result.insertedId);
// Read const user = await users.findOne({ username: 'john_doe' });
const allActive = await users.find({ status: 'active' }) .sort({ createdAt: -1 }) .limit(10) .toArray();
// Update await users.updateOne( { username: 'john_doe' }, { $set: { age: 31 } } );
// Delete await users.deleteOne({ username: 'john_doe' });
await client.close();}
main().catch(console.error);💡 Best Practices
Заголовок раздела «💡 Best Practices»- Индексы: Создавайте на часто используемых полях для поиска
- _id: Используйте ObjectId для автоматической генерации уникальных ID
- Схема: Хоть MongoDB schema-less, определяйте структуру в коде (Mongoose/Zod)
- Денормализация: Embed связанные данные вместо JOIN’ов
- Проекция: Выбирайте только нужные поля
- Connection Pooling: Переиспользуйте подключения
Когда использовать MongoDB
Заголовок раздела «Когда использовать MongoDB»✅ Хорошо для:
- Гибкая/меняющаяся схема
- Быстрая разработка (прототипы)
- Логи, события, аналитика
- Каталоги, CMS
- Реал-тайм приложения
- IoT данные
❌ Плохо для:
- Сложные транзакции (банки)
- Многие JOIN’ы
- Строгая консистентность
- Реляционные данные
⚠️ Частые ошибки
Заголовок раздела «⚠️ Частые ошибки»- Отсутствие индексов (медленные запросы)
- Чрезмерная вложенность документов
- Игнорирование размера документа (16MB лимит)
- Неправильное моделирование данных
Следующий урок: Aggregation Pipeline в MongoDB →