MongoDB代码实践:从基础操作到高级应用

MongoDB代码实践:从基础操作到高级应用

MongoDB作为非关系型数据库的代表,以其灵活的文档模型和强大的横向扩展能力,成为现代应用开发的重要选择。本文将从基础CRUD操作出发,逐步深入索引优化、聚合查询、事务处理等高级场景,结合实际代码示例与性能优化建议,帮助开发者构建高效可靠的MongoDB应用。

一、基础CRUD操作:代码规范与最佳实践

1.1 连接管理与错误处理

MongoDB的Node.js驱动提供了直观的连接API,但需注意连接池配置与错误重试机制:

  1. const { MongoClient } = require('mongodb');
  2. const uri = 'mongodb://localhost:27017';
  3. const client = new MongoClient(uri, {
  4. maxPoolSize: 50, // 连接池大小
  5. retryWrites: true, // 写操作重试
  6. retryReads: true // 读操作重试
  7. });
  8. async function connect() {
  9. try {
  10. await client.connect();
  11. console.log('Connected to MongoDB');
  12. return client.db('testDB');
  13. } catch (err) {
  14. console.error('Connection error:', err);
  15. process.exit(1);
  16. }
  17. }

关键点

  • 连接池大小需根据并发量调整(建议10-100)
  • 生产环境应配置connectTimeoutMSsocketTimeoutMS
  • 使用async/await避免回调地狱

1.2 文档操作代码示例

插入文档

  1. async function insertUser(db, userData) {
  2. const collection = db.collection('users');
  3. const result = await collection.insertOne({
  4. ...userData,
  5. createdAt: new Date(),
  6. updatedAt: new Date()
  7. });
  8. return result.insertedId;
  9. }

查询文档

  1. async function findUserById(db, userId) {
  2. const collection = db.collection('users');
  3. return await collection.findOne({ _id: new ObjectId(userId) });
  4. }

更新文档

  1. async function updateUserEmail(db, userId, newEmail) {
  2. const collection = db.collection('users');
  3. const result = await collection.updateOne(
  4. { _id: new ObjectId(userId) },
  5. { $set: { email: newEmail, updatedAt: new Date() } }
  6. );
  7. return result.modifiedCount;
  8. }

删除文档

  1. async function deleteUser(db, userId) {
  2. const collection = db.collection('users');
  3. const result = await collection.deleteOne({ _id: new ObjectId(userId) });
  4. return result.deletedCount;
  5. }

最佳实践

  • 始终使用ObjectId处理_id字段
  • 批量操作优先选择insertMany/updateMany
  • 更新时使用$set避免覆盖整个文档

二、索引优化:代码实现与性能分析

2.1 索引创建与管理

  1. async function createIndexes(db) {
  2. const collection = db.collection('orders');
  3. await collection.createIndexes([
  4. { key: { userId: 1 }, name: 'userId_idx' }, // 单字段索引
  5. { key: { status: 1, createdAt: -1 }, name: 'status_createdAt_idx' }, // 复合索引
  6. { key: { 'address.city': 1 }, name: 'city_idx' }, // 嵌套字段索引
  7. { key: { email: 1 }, unique: true } // 唯一索引
  8. ]);
  9. }

索引策略

  • 查询频率高的字段优先建索引
  • 复合索引遵循”最左前缀”原则
  • 写密集型场景需权衡索引数量(建议不超过5个/集合)

2.2 索引性能监控

  1. async function analyzeIndexes(db) {
  2. const collection = db.collection('orders');
  3. const stats = await collection.stats();
  4. const indexStats = await collection.indexes();
  5. console.log('Index sizes:', indexStats.map(idx => ({
  6. name: idx.name,
  7. size: idx.stats?.size || 'N/A'
  8. })));
  9. // 解释查询执行计划
  10. const explain = await collection.find({ status: 'pending' })
  11. .explain('executionStats');
  12. console.log('Query execution stats:', explain.executionStats);
  13. }

优化建议

  • 使用explain()分析查询是否命中索引
  • 定期运行db.collection.aggregate([{ $indexStats: {} }])监控索引使用率
  • 删除未使用的索引(可通过$indexStats识别)

三、聚合查询:复杂场景代码实现

3.1 基础聚合管道

  1. async function getUserOrderStats(db, userId) {
  2. const pipeline = [
  3. { $match: { userId: new ObjectId(userId) } },
  4. { $group: {
  5. _id: '$status',
  6. count: { $sum: 1 },
  7. totalAmount: { $sum: '$amount' }
  8. }},
  9. { $sort: { totalAmount: -1 } },
  10. { $project: {
  11. status: '$_id',
  12. count: 1,
  13. totalAmount: 1,
  14. _id: 0
  15. }}
  16. ];
  17. return await db.collection('orders').aggregate(pipeline).toArray();
  18. }

3.2 高级聚合技巧

日期范围统计

  1. async function getDailySales(db, startDate, endDate) {
  2. const pipeline = [
  3. { $match: {
  4. createdAt: { $gte: new Date(startDate), $lte: new Date(endDate) }
  5. }},
  6. { $project: {
  7. date: { $dateToString: { format: '%Y-%m-%d', date: '$createdAt' } },
  8. amount: 1
  9. }},
  10. { $group: {
  11. _id: '$date',
  12. total: { $sum: '$amount' },
  13. avg: { $avg: '$amount' }
  14. }},
  15. { $sort: { _id: 1 } }
  16. ];
  17. return await db.collection('orders').aggregate(pipeline).toArray();
  18. }

优化建议

  • 复杂聚合前先用$match减少处理数据量
  • 合理使用$project控制字段数量
  • 内存密集型操作(如$sort)需设置allowDiskUse: true

四、事务处理:多文档操作代码示例

4.1 会话管理实现

  1. async function transferFunds(db, fromId, toId, amount) {
  2. const session = db.client.startSession();
  3. try {
  4. session.startTransaction({
  5. readConcern: { level: 'local' },
  6. writeConcern: { w: 'majority' }
  7. });
  8. const accounts = db.collection('accounts');
  9. const fromAccount = await accounts.findOne({
  10. _id: new ObjectId(fromId),
  11. balance: { $gte: amount }
  12. }, { session });
  13. if (!fromAccount) throw new Error('Insufficient funds');
  14. await accounts.updateOne(
  15. { _id: new ObjectId(fromId) },
  16. { $inc: { balance: -amount } },
  17. { session }
  18. );
  19. await accounts.updateOne(
  20. { _id: new ObjectId(toId) },
  21. { $inc: { balance: amount } },
  22. { session }
  23. );
  24. await session.commitTransaction();
  25. return true;
  26. } catch (err) {
  27. await session.abortTransaction();
  28. console.error('Transaction failed:', err);
  29. throw err;
  30. } finally {
  31. session.endSession();
  32. }
  33. }

4.2 事务使用准则

适用场景

  • 多文档原子性操作
  • 跨集合更新
  • 金融类敏感操作

注意事项

  • 事务操作会增加服务器负载
  • 单个事务中操作建议不超过1000个文档
  • 生产环境应配置适当的readConcernwriteConcern

五、性能优化:代码级调优策略

5.1 查询优化技巧

  1. // 优化前:全表扫描
  2. db.collection('products').find({ price: { $gt: 100 } });
  3. // 优化后:使用索引
  4. db.collection('products').find({
  5. price: { $gt: 100 },
  6. category: 'electronics' // 添加选择性高的过滤条件
  7. }).sort({ price: 1 });

优化建议

  • 使用$gt/$lt等范围查询时,确保索引顺序正确
  • 避免在查询条件中使用计算字段(如{$gt: [new Date(), -86400000]}
  • 投影只返回必要字段

5.2 批量操作优化

  1. // 低效方式:循环单条插入
  2. async function inefficientInsert(db, users) {
  3. for (const user of users) {
  4. await db.collection('users').insertOne(user);
  5. }
  6. }
  7. // 高效方式:批量插入
  8. async function efficientInsert(db, users) {
  9. const chunks = [];
  10. for (let i = 0; i < users.length; i += 1000) {
  11. chunks.push(users.slice(i, i + 1000));
  12. }
  13. for (const chunk of chunks) {
  14. await db.collection('users').insertMany(chunk);
  15. }
  16. }

批量操作建议

  • 批量大小控制在500-1000条/次
  • 错误处理需区分部分失败和全部失败
  • 使用ordered: false提高并行度(允许部分失败)

六、安全实践:代码防护措施

6.1 防注入攻击

  1. // 危险方式:直接拼接查询
  2. function unsafeFind(db, email) {
  3. return db.collection('users').find({ email }); // 若email来自用户输入
  4. }
  5. // 安全方式:使用参数化查询
  6. async function safeFind(db, email) {
  7. // MongoDB驱动默认使用参数化查询
  8. // 但需验证输入格式
  9. if (!/^[^\s@]+@[^\s@]+\.[^\s@]+$/.test(email)) {
  10. throw new Error('Invalid email format');
  11. }
  12. return await db.collection('users').findOne({ email });
  13. }

6.2 敏感数据保护

  1. async function getUserProfile(db, userId, isAdmin) {
  2. const projection = isAdmin ? {} : {
  3. password: 0,
  4. ssn: 0,
  5. creditCard: 0
  6. };
  7. return await db.collection('users')
  8. .findOne({ _id: new ObjectId(userId) }, { projection });
  9. }

安全建议

  • 实施最小权限原则
  • 使用字段投影排除敏感数据
  • 定期轮换API密钥和数据库凭证

七、部署与运维:代码相关实践

7.1 变更流(Change Streams)实现

  1. async function watchCollectionChanges(db) {
  2. const collection = db.collection('inventory');
  3. const changeStream = collection.watch();
  4. changeStream.on('change', (change) => {
  5. console.log('Change detected:', change);
  6. // 可根据change.operationType处理不同操作
  7. });
  8. // 保持连接活跃
  9. setInterval(() => {}, 10000);
  10. }

应用场景

  • 实时数据同步
  • 触发微服务事件
  • 审计日志记录

7.2 连接字符串加密

  1. // 生产环境应从安全存储获取凭据
  2. const encryptedUri = process.env.MONGODB_ENCRYPTED_URI;
  3. const decryptionKey = process.env.DECRYPTION_KEY;
  4. // 实际应用中应使用专业的密钥管理系统
  5. function decryptUri(encrypted, key) {
  6. // 伪代码:实际实现需使用AES等加密算法
  7. return encrypted.replace('__ENC__', '');
  8. }
  9. const uri = decryptUri(encryptedUri, decryptionKey);
  10. const client = new MongoClient(uri);

总结

MongoDB的代码实现需要兼顾功能实现与性能优化。从基础CRUD操作到高级事务处理,每个环节都存在优化空间。开发者应遵循以下原则:

  1. 合理设计文档结构减少更新操作
  2. 通过索引策略平衡查询性能与写入开销
  3. 利用聚合框架处理复杂分析需求
  4. 实施严格的安全防护措施
  5. 结合业务场景选择合适的部署架构

实际应用中,建议通过压力测试验证代码性能,并持续监控数据库指标。对于高并发场景,可考虑分片集群部署;对于数据一致性要求高的业务,需仔细设计事务边界。随着MongoDB版本升级(如5.0+的时间序列集合、6.0+的集群查询优化),开发者应保持对新技术特性的关注,及时调整代码实现以获得最佳性能。