MongoDB 数据库设计:NoSQL 数据建模最佳实践

深入探讨 MongoDB 数据库设计原则、数据建模策略、性能优化技巧和最佳实践,帮助开发者构建高效、可扩展的 NoSQL 应用。

2025年9月18日
DocsLib Team
MongoDBNoSQL数据库设计数据建模性能优化

MongoDB 数据库设计:NoSQL 数据建模最佳实践

MongoDB 简介

MongoDB 是一个基于文档的 NoSQL 数据库,它使用 BSON(Binary JSON)格式存储数据。与传统的关系型数据库不同,MongoDB 提供了更灵活的数据模型和水平扩展能力。

MongoDB 的核心特性

  1. 文档导向:数据以文档形式存储,支持嵌套结构
  2. 动态模式:无需预定义严格的表结构
  3. 水平扩展:支持分片和副本集
  4. 丰富的查询语言:支持复杂的查询和聚合操作
  5. 索引支持:多种索引类型提升查询性能
  6. ACID 事务:支持多文档事务

数据建模基础

文档结构设计

// 用户文档示例
{
  "_id": ObjectId("507f1f77bcf86cd799439011"),
  "username": "john_doe",
  "email": "john@example.com",
  "profile": {
    "firstName": "John",
    "lastName": "Doe",
    "avatar": "https://example.com/avatar.jpg",
    "bio": "Software developer passionate about technology",
    "location": {
      "city": "San Francisco",
      "country": "USA",
      "coordinates": {
        "lat": 37.7749,
        "lng": -122.4194
      }
    }
  },
  "preferences": {
    "theme": "dark",
    "language": "en",
    "notifications": {
      "email": true,
      "push": false,
      "sms": false
    }
  },
  "tags": ["developer", "javascript", "mongodb"],
  "socialLinks": [
    {
      "platform": "github",
      "url": "https://github.com/johndoe"
    },
    {
      "platform": "linkedin",
      "url": "https://linkedin.com/in/johndoe"
    }
  ],
  "createdAt": ISODate("2023-01-15T10:30:00Z"),
  "updatedAt": ISODate("2024-07-30T14:20:00Z"),
  "lastLoginAt": ISODate("2024-07-30T09:15:00Z"),
  "isActive": true,
  "role": "user"
}

// 博客文章文档示例
{
  "_id": ObjectId("507f1f77bcf86cd799439012"),
  "title": "Getting Started with MongoDB",
  "slug": "getting-started-with-mongodb",
  "content": "MongoDB is a powerful NoSQL database...",
  "excerpt": "Learn the basics of MongoDB database design",
  "author": {
    "_id": ObjectId("507f1f77bcf86cd799439011"),
    "username": "john_doe",
    "displayName": "John Doe"
  },
  "category": {
    "_id": ObjectId("507f1f77bcf86cd799439013"),
    "name": "Database",
    "slug": "database"
  },
  "tags": [
    {
      "_id": ObjectId("507f1f77bcf86cd799439014"),
      "name": "MongoDB",
      "slug": "mongodb"
    },
    {
      "_id": ObjectId("507f1f77bcf86cd799439015"),
      "name": "NoSQL",
      "slug": "nosql"
    }
  ],
  "status": "published",
  "publishedAt": ISODate("2024-07-30T12:00:00Z"),
  "createdAt": ISODate("2024-07-29T15:30:00Z"),
  "updatedAt": ISODate("2024-07-30T11:45:00Z"),
  "metadata": {
    "readTime": 8,
    "wordCount": 1500,
    "views": 245,
    "likes": 18,
    "shares": 5
  },
  "seo": {
    "metaTitle": "Getting Started with MongoDB - Complete Guide",
    "metaDescription": "Learn MongoDB basics, data modeling, and best practices",
    "keywords": ["mongodb", "nosql", "database", "tutorial"]
  },
  "comments": [
    {
      "_id": ObjectId("507f1f77bcf86cd799439016"),
      "author": {
        "_id": ObjectId("507f1f77bcf86cd799439017"),
        "username": "jane_smith",
        "displayName": "Jane Smith"
      },
      "content": "Great article! Very helpful for beginners.",
      "createdAt": ISODate("2024-07-30T13:15:00Z"),
      "likes": 3,
      "replies": [
        {
          "_id": ObjectId("507f1f77bcf86cd799439018"),
          "author": {
            "_id": ObjectId("507f1f77bcf86cd799439011"),
            "username": "john_doe",
            "displayName": "John Doe"
          },
          "content": "Thank you! Glad you found it useful.",
          "createdAt": ISODate("2024-07-30T14:00:00Z")
        }
      ]
    }
  ]
}

嵌入 vs 引用

嵌入式设计(Embedding)

// 适合一对少量的关系
// 用户和地址信息
{
  "_id": ObjectId("..."),
  "username": "john_doe",
  "email": "john@example.com",
  "addresses": [
    {
      "type": "home",
      "street": "123 Main St",
      "city": "San Francisco",
      "state": "CA",
      "zipCode": "94102",
      "country": "USA",
      "isDefault": true
    },
    {
      "type": "work",
      "street": "456 Business Ave",
      "city": "San Francisco",
      "state": "CA",
      "zipCode": "94105",
      "country": "USA",
      "isDefault": false
    }
  ]
}

// 订单和订单项
{
  "_id": ObjectId("..."),
  "orderNumber": "ORD-2024-001",
  "customerId": ObjectId("..."),
  "items": [
    {
      "productId": ObjectId("..."),
      "productName": "Laptop",
      "sku": "LAP-001",
      "quantity": 1,
      "unitPrice": 999.99,
      "totalPrice": 999.99
    },
    {
      "productId": ObjectId("..."),
      "productName": "Mouse",
      "sku": "MOU-001",
      "quantity": 2,
      "unitPrice": 29.99,
      "totalPrice": 59.98
    }
  ],
  "totalAmount": 1059.97,
  "status": "pending",
  "createdAt": ISODate("2024-07-30T10:00:00Z")
}

引用式设计(Referencing)

// 适合一对多或多对多的关系
// 用户集合
{
  "_id": ObjectId("507f1f77bcf86cd799439011"),
  "username": "john_doe",
  "email": "john@example.com",
  "profile": {
    "firstName": "John",
    "lastName": "Doe"
  }
}

// 文章集合
{
  "_id": ObjectId("507f1f77bcf86cd799439012"),
  "title": "MongoDB Best Practices",
  "content": "...",
  "authorId": ObjectId("507f1f77bcf86cd799439011"),
  "categoryId": ObjectId("507f1f77bcf86cd799439013"),
  "tagIds": [
    ObjectId("507f1f77bcf86cd799439014"),
    ObjectId("507f1f77bcf86cd799439015")
  ],
  "createdAt": ISODate("2024-07-30T12:00:00Z")
}

// 分类集合
{
  "_id": ObjectId("507f1f77bcf86cd799439013"),
  "name": "Database",
  "slug": "database",
  "description": "Database related articles"
}

// 标签集合
{
  "_id": ObjectId("507f1f77bcf86cd799439014"),
  "name": "MongoDB",
  "slug": "mongodb",
  "color": "#4DB33D"
}

混合设计模式

// 电商产品设计 - 混合嵌入和引用
{
  "_id": ObjectId("..."),
  "name": "MacBook Pro 16-inch",
  "sku": "MBP-16-2024",
  "description": "Powerful laptop for professionals",
  "price": {
    "current": 2499.99,
    "original": 2699.99,
    "currency": "USD"
  },
  "category": {
    "_id": ObjectId("..."),
    "name": "Laptops",
    "path": "Electronics > Computers > Laptops"
  },
  "brand": {
    "_id": ObjectId("..."),
    "name": "Apple",
    "logo": "https://example.com/apple-logo.png"
  },
  "specifications": {
    "processor": "M3 Pro chip",
    "memory": "18GB unified memory",
    "storage": "512GB SSD",
    "display": "16.2-inch Liquid Retina XDR",
    "weight": "2.1 kg",
    "dimensions": {
      "width": 35.57,
      "height": 24.81,
      "depth": 1.68,
      "unit": "cm"
    }
  },
  "images": [
    {
      "url": "https://example.com/mbp-main.jpg",
      "alt": "MacBook Pro main view",
      "isPrimary": true
    },
    {
      "url": "https://example.com/mbp-side.jpg",
      "alt": "MacBook Pro side view",
      "isPrimary": false
    }
  ],
  "inventory": {
    "quantity": 25,
    "reserved": 3,
    "available": 22,
    "lowStockThreshold": 5
  },
  "ratings": {
    "average": 4.7,
    "count": 156,
    "distribution": {
      "5": 98,
      "4": 42,
      "3": 12,
      "2": 3,
      "1": 1
    }
  },
  "tags": ["laptop", "apple", "professional", "m3-pro"],
  "status": "active",
  "createdAt": ISODate("2024-01-15T10:00:00Z"),
  "updatedAt": ISODate("2024-07-30T14:30:00Z")
}

// 产品评论单独存储(引用)
{
  "_id": ObjectId("..."),
  "productId": ObjectId("..."),
  "userId": ObjectId("..."),
  "rating": 5,
  "title": "Excellent laptop for development",
  "content": "The M3 Pro chip is incredibly fast...",
  "verified": true,
  "helpful": {
    "yes": 23,
    "no": 2
  },
  "createdAt": ISODate("2024-07-25T16:20:00Z")
}

索引策略

基础索引

// 创建单字段索引
db.users.createIndex({ "email": 1 })  // 升序
db.users.createIndex({ "createdAt": -1 })  // 降序

// 创建复合索引
db.posts.createIndex({ "authorId": 1, "createdAt": -1 })
db.products.createIndex({ "category.name": 1, "price.current": 1 })

// 创建文本索引(全文搜索)
db.posts.createIndex({
  "title": "text",
  "content": "text",
  "tags": "text"
}, {
  "weights": {
    "title": 10,
    "content": 5,
    "tags": 1
  },
  "name": "post_text_index"
})

// 创建地理空间索引
db.locations.createIndex({ "coordinates": "2dsphere" })

// 创建哈希索引(用于分片)
db.users.createIndex({ "_id": "hashed" })

// 创建部分索引(只索引满足条件的文档)
db.users.createIndex(
  { "email": 1 },
  { "partialFilterExpression": { "isActive": true } }
)

// 创建稀疏索引(忽略不包含索引字段的文档)
db.users.createIndex(
  { "phoneNumber": 1 },
  { "sparse": true }
)

// 创建唯一索引
db.users.createIndex(
  { "email": 1 },
  { "unique": true }
)

// 创建 TTL 索引(自动删除过期文档)
db.sessions.createIndex(
  { "expiresAt": 1 },
  { "expireAfterSeconds": 0 }
)

索引优化策略

// 查询性能分析
// 使用 explain() 分析查询计划
db.posts.find({ "authorId": ObjectId("..."), "status": "published" })
  .sort({ "createdAt": -1 })
  .explain("executionStats")

// 索引使用统计
db.posts.aggregate([
  { $indexStats: {} }
])

// 查找未使用的索引
db.runCommand({ "collStats": "posts", "indexDetails": true })

// 复合索引的字段顺序很重要
// 好的索引设计
db.orders.createIndex({
  "customerId": 1,     // 等值查询
  "status": 1,        // 等值查询
  "createdAt": -1     // 排序字段
})

// 支持的查询模式
db.orders.find({ "customerId": ObjectId("...") })
db.orders.find({ "customerId": ObjectId("..."), "status": "pending" })
db.orders.find({ "customerId": ObjectId("..."), "status": "pending" })
  .sort({ "createdAt": -1 })

// ESR 规则:Equality, Sort, Range
// 等值查询 -> 排序 -> 范围查询
db.events.createIndex({
  "userId": 1,           // Equality
  "createdAt": -1,      // Sort
  "priority": 1         // Range
})

查询优化

高效查询模式

// 使用投影减少网络传输
db.users.find(
  { "isActive": true },
  { "username": 1, "email": 1, "profile.firstName": 1, "profile.lastName": 1 }
)

// 使用 limit() 限制结果数量
db.posts.find({ "status": "published" })
  .sort({ "createdAt": -1 })
  .limit(20)

// 使用 skip() 进行分页(注意性能问题)
db.posts.find({ "status": "published" })
  .sort({ "createdAt": -1 })
  .skip(20)
  .limit(20)

// 更好的分页方式:基于游标的分页
// 第一页
db.posts.find({ "status": "published" })
  .sort({ "_id": -1 })
  .limit(20)

// 后续页面
db.posts.find({
  "status": "published",
  "_id": { $lt: ObjectId("lastIdFromPreviousPage") }
})
  .sort({ "_id": -1 })
  .limit(20)

// 使用聚合管道进行复杂查询
db.orders.aggregate([
  // 匹配条件
  {
    $match: {
      "createdAt": {
        $gte: ISODate("2024-01-01T00:00:00Z"),
        $lt: ISODate("2024-08-01T00:00:00Z")
      },
      "status": { $in: ["completed", "shipped"] }
    }
  },
  // 关联用户信息
  {
    $lookup: {
      "from": "users",
      "localField": "customerId",
      "foreignField": "_id",
      "as": "customer"
    }
  },
  // 展开数组
  {
    $unwind: "$customer"
  },
  // 分组统计
  {
    $group: {
      "_id": {
        "month": { $month: "$createdAt" },
        "year": { $year: "$createdAt" }
      },
      "totalOrders": { $sum: 1 },
      "totalRevenue": { $sum: "$totalAmount" },
      "averageOrderValue": { $avg: "$totalAmount" },
      "uniqueCustomers": { $addToSet: "$customerId" }
    }
  },
  // 计算唯一客户数
  {
    $addFields: {
      "uniqueCustomerCount": { $size: "$uniqueCustomers" }
    }
  },
  // 排序
  {
    $sort: { "_id.year": 1, "_id.month": 1 }
  },
  // 格式化输出
  {
    $project: {
      "_id": 0,
      "period": {
        $concat: [
          { $toString: "$_id.year" },
          "-",
          {
            $cond: {
              "if": { $lt: ["$_id.month", 10] },
              "then": { $concat: ["0", { $toString: "$_id.month" }] },
              "else": { $toString: "$_id.month" }
            }
          }
        ]
      },
      "totalOrders": 1,
      "totalRevenue": { $round: ["$totalRevenue", 2] },
      "averageOrderValue": { $round: ["$averageOrderValue", 2] },
      "uniqueCustomerCount": 1
    }
  }
])

查询性能监控

// 启用查询分析器
db.setProfilingLevel(2, { slowms: 100 })

// 查看慢查询
db.system.profile.find().sort({ ts: -1 }).limit(5)

// 分析特定查询
db.posts.find({ "authorId": ObjectId("...") })
  .explain("executionStats")

// 查看索引使用情况
db.posts.aggregate([
  { $indexStats: {} },
  { $sort: { "accesses.ops": -1 } }
])

// 监控集合统计信息
db.posts.stats()

// 查看当前操作
db.currentOp()

// 终止长时间运行的操作
db.killOp(operationId)

数据建模模式

1. 属性模式(Attribute Pattern)

// 传统方式:每个属性一个字段
{
  "_id": ObjectId("..."),
  "name": "iPhone 15 Pro",
  "color": "Space Black",
  "storage": "256GB",
  "screen_size": "6.1 inch",
  "weight": "187g",
  "battery_life": "23 hours",
  "camera_megapixels": "48MP",
  "water_resistance": "IP68"
}

// 属性模式:使用数组存储属性
{
  "_id": ObjectId("..."),
  "name": "iPhone 15 Pro",
  "attributes": [
    { "key": "color", "value": "Space Black", "type": "string" },
    { "key": "storage", "value": 256, "type": "number", "unit": "GB" },
    { "key": "screen_size", "value": 6.1, "type": "number", "unit": "inch" },
    { "key": "weight", "value": 187, "type": "number", "unit": "g" },
    { "key": "battery_life", "value": 23, "type": "number", "unit": "hours" },
    { "key": "camera_megapixels", "value": 48, "type": "number", "unit": "MP" },
    { "key": "water_resistance", "value": "IP68", "type": "string" }
  ]
}

// 创建索引支持属性查询
db.products.createIndex({ "attributes.key": 1, "attributes.value": 1 })

// 查询示例
db.products.find({
  "attributes": {
    $elemMatch: {
      "key": "storage",
      "value": { $gte: 128 }
    }
  }
})

2. 桶模式(Bucket Pattern)

// 时间序列数据的桶模式
// 传统方式:每个数据点一个文档
{
  "_id": ObjectId("..."),
  "sensorId": "sensor_001",
  "timestamp": ISODate("2024-07-30T10:00:00Z"),
  "temperature": 23.5,
  "humidity": 65.2,
  "pressure": 1013.25
}

// 桶模式:按时间段分组
{
  "_id": ObjectId("..."),
  "sensorId": "sensor_001",
  "bucketStart": ISODate("2024-07-30T10:00:00Z"),
  "bucketEnd": ISODate("2024-07-30T11:00:00Z"),
  "count": 60,
  "measurements": [
    {
      "timestamp": ISODate("2024-07-30T10:00:00Z"),
      "temperature": 23.5,
      "humidity": 65.2,
      "pressure": 1013.25
    },
    {
      "timestamp": ISODate("2024-07-30T10:01:00Z"),
      "temperature": 23.6,
      "humidity": 65.1,
      "pressure": 1013.30
    }
    // ... 更多测量数据
  ],
  "summary": {
    "temperature": {
      "min": 23.1,
      "max": 24.2,
      "avg": 23.7
    },
    "humidity": {
      "min": 64.5,
      "max": 66.8,
      "avg": 65.4
    },
    "pressure": {
      "min": 1012.8,
      "max": 1014.1,
      "avg": 1013.5
    }
  }
}

// 网站访问日志的桶模式
{
  "_id": ObjectId("..."),
  "date": ISODate("2024-07-30T00:00:00Z"),
  "hour": 14,
  "visits": [
    {
      "minute": 0,
      "pageViews": 145,
      "uniqueVisitors": 98,
      "bounceRate": 0.32
    },
    {
      "minute": 1,
      "pageViews": 152,
      "uniqueVisitors": 103,
      "bounceRate": 0.29
    }
    // ... 60分钟的数据
  ],
  "hourlyTotal": {
    "pageViews": 8750,
    "uniqueVisitors": 5420,
    "averageBounceRate": 0.31
  }
}

3. 子集模式(Subset Pattern)

// 电影信息 - 主文档
{
  "_id": ObjectId("..."),
  "title": "The Matrix",
  "year": 1999,
  "director": "The Wachowskis",
  "genre": ["Action", "Sci-Fi"],
  "rating": 8.7,
  "poster": "https://example.com/matrix-poster.jpg",
  "plot": "A computer programmer is led to fight an underground war...",
  "runtime": 136,
  "cast": [
    {
      "actor": "Keanu Reeves",
      "character": "Neo",
      "order": 1
    },
    {
      "actor": "Laurence Fishburne",
      "character": "Morpheus",
      "order": 2
    }
    // 只存储主要演员
  ],
  "recentReviews": [
    {
      "_id": ObjectId("..."),
      "userId": ObjectId("..."),
      "username": "moviefan123",
      "rating": 9,
      "comment": "Groundbreaking film!",
      "createdAt": ISODate("2024-07-29T15:30:00Z")
    }
    // 只存储最近的几条评论
  ],
  "stats": {
    "totalReviews": 15420,
    "averageRating": 8.7,
    "totalCast": 45
  }
}

// 完整演员信息 - 单独集合
{
  "_id": ObjectId("..."),
  "movieId": ObjectId("..."),
  "cast": [
    {
      "actor": "Keanu Reeves",
      "character": "Neo",
      "order": 1,
      "bio": "Canadian actor known for...",
      "filmography": [...]
    }
    // 完整的演员列表和详细信息
  ]
}

// 完整评论信息 - 单独集合
{
  "_id": ObjectId("..."),
  "movieId": ObjectId("..."),
  "userId": ObjectId("..."),
  "rating": 9,
  "title": "A masterpiece of cinema",
  "content": "This movie changed everything...",
  "helpful": 45,
  "notHelpful": 3,
  "createdAt": ISODate("2024-07-29T15:30:00Z")
}

4. 计算模式(Computed Pattern)

// 用户统计信息
{
  "_id": ObjectId("..."),
  "userId": ObjectId("..."),
  "username": "john_doe",
  "stats": {
    "postsCount": 156,
    "commentsCount": 892,
    "likesReceived": 2340,
    "followersCount": 1250,
    "followingCount": 340,
    "lastUpdated": ISODate("2024-07-30T14:30:00Z")
  },
  "monthlyStats": {
    "2024-07": {
      "postsCount": 12,
      "commentsCount": 67,
      "likesReceived": 189
    },
    "2024-06": {
      "postsCount": 15,
      "commentsCount": 78,
      "likesReceived": 234
    }
  }
}

// 产品销售统计
{
  "_id": ObjectId("..."),
  "productId": ObjectId("..."),
  "sku": "LAP-001",
  "salesStats": {
    "totalSold": 1250,
    "totalRevenue": 1249875.00,
    "averagePrice": 999.90,
    "lastSaleDate": ISODate("2024-07-30T13:45:00Z"),
    "lastUpdated": ISODate("2024-07-30T14:00:00Z")
  },
  "dailyStats": {
    "2024-07-30": {
      "sold": 8,
      "revenue": 7999.20
    },
    "2024-07-29": {
      "sold": 12,
      "revenue": 11998.80
    }
  }
}

// 使用聚合管道更新计算字段
db.users.aggregate([
  {
    $lookup: {
      "from": "posts",
      "localField": "_id",
      "foreignField": "authorId",
      "as": "posts"
    }
  },
  {
    $lookup: {
      "from": "comments",
      "localField": "_id",
      "foreignField": "authorId",
      "as": "comments"
    }
  },
  {
    $addFields: {
      "stats.postsCount": { $size: "$posts" },
      "stats.commentsCount": { $size: "$comments" },
      "stats.lastUpdated": new Date()
    }
  },
  {
    $merge: {
      "into": "users",
      "whenMatched": "merge"
    }
  }
])

事务处理

单文档事务

// MongoDB 中的单文档操作是原子性的
db.accounts.updateOne(
  { "_id": ObjectId("...") },
  {
    $inc: { "balance": -100 },
    $push: {
      "transactions": {
        "_id": ObjectId(),
        "type": "debit",
        "amount": 100,
        "description": "Transfer to savings",
        "timestamp": new Date()
      }
    }
  }
)

多文档事务

// 银行转账示例
const session = db.getMongo().startSession()

try {
  session.startTransaction()
  
  const accounts = session.getDatabase("bank").getCollection("accounts")
  
  // 从源账户扣款
  const debitResult = accounts.updateOne(
    { "_id": ObjectId("sourceAccountId"), "balance": { $gte: 1000 } },
    {
      $inc: { "balance": -1000 },
      $push: {
        "transactions": {
          "_id": ObjectId(),
          "type": "debit",
          "amount": 1000,
          "toAccount": ObjectId("targetAccountId"),
          "description": "Transfer",
          "timestamp": new Date()
        }
      }
    },
    { session }
  )
  
  if (debitResult.matchedCount === 0) {
    throw new Error("Insufficient funds or account not found")
  }
  
  // 向目标账户存款
  const creditResult = accounts.updateOne(
    { "_id": ObjectId("targetAccountId") },
    {
      $inc: { "balance": 1000 },
      $push: {
        "transactions": {
          "_id": ObjectId(),
          "type": "credit",
          "amount": 1000,
          "fromAccount": ObjectId("sourceAccountId"),
          "description": "Transfer received",
          "timestamp": new Date()
        }
      }
    },
    { session }
  )
  
  if (creditResult.matchedCount === 0) {
    throw new Error("Target account not found")
  }
  
  // 记录转账日志
  const transfers = session.getDatabase("bank").getCollection("transfers")
  transfers.insertOne({
    "_id": ObjectId(),
    "fromAccount": ObjectId("sourceAccountId"),
    "toAccount": ObjectId("targetAccountId"),
    "amount": 1000,
    "status": "completed",
    "timestamp": new Date()
  }, { session })
  
  session.commitTransaction()
  console.log("Transfer completed successfully")
  
} catch (error) {
  session.abortTransaction()
  console.error("Transfer failed:", error.message)
} finally {
  session.endSession()
}

事务最佳实践

// 使用重试逻辑处理事务冲突
function withRetry(operation, maxRetries = 3) {
  return new Promise(async (resolve, reject) => {
    let retries = 0
    
    while (retries < maxRetries) {
      const session = db.getMongo().startSession()
      
      try {
        session.startTransaction()
        const result = await operation(session)
        await session.commitTransaction()
        session.endSession()
        resolve(result)
        return
      } catch (error) {
        await session.abortTransaction()
        session.endSession()
        
        if (error.hasErrorLabel("TransientTransactionError") && retries < maxRetries - 1) {
          retries++
          console.log(`Transaction failed, retrying... (${retries}/${maxRetries})`)
          await new Promise(resolve => setTimeout(resolve, Math.pow(2, retries) * 100))
        } else {
          reject(error)
          return
        }
      }
    }
  })
}

// 使用示例
withRetry(async (session) => {
  const orders = session.getDatabase("ecommerce").getCollection("orders")
  const inventory = session.getDatabase("ecommerce").getCollection("inventory")
  
  // 创建订单
  const order = {
    "_id": ObjectId(),
    "customerId": ObjectId("..."),
    "items": [
      {
        "productId": ObjectId("..."),
        "quantity": 2,
        "price": 99.99
      }
    ],
    "total": 199.98,
    "status": "pending",
    "createdAt": new Date()
  }
  
  await orders.insertOne(order, { session })
  
  // 更新库存
  for (const item of order.items) {
    const updateResult = await inventory.updateOne(
      {
        "productId": item.productId,
        "quantity": { $gte: item.quantity }
      },
      {
        $inc: { "quantity": -item.quantity },
        $push: {
          "reservations": {
            "orderId": order._id,
            "quantity": item.quantity,
            "timestamp": new Date()
          }
        }
      },
      { session }
    )
    
    if (updateResult.matchedCount === 0) {
      throw new Error(`Insufficient inventory for product ${item.productId}`)
    }
  }
  
  return order._id
})
.then(orderId => {
  console.log("Order created successfully:", orderId)
})
.catch(error => {
  console.error("Order creation failed:", error.message)
})

性能优化

读取优化

// 使用投影减少数据传输
db.users.find(
  { "isActive": true },
  {
    "username": 1,
    "email": 1,
    "profile.firstName": 1,
    "profile.lastName": 1,
    "profile.avatar": 1
  }
)

// 使用 allowDiskUse 处理大数据集排序
db.orders.aggregate([
  { $match: { "createdAt": { $gte: ISODate("2024-01-01") } } },
  { $sort: { "totalAmount": -1 } },
  { $limit: 100 }
], { allowDiskUse: true })

// 使用 hint 强制使用特定索引
db.posts.find({ "authorId": ObjectId("...") })
  .hint({ "authorId": 1, "createdAt": -1 })

// 使用 readConcern 控制读取一致性
db.orders.find({ "status": "pending" })
  .readConcern("majority")

// 批量读取优化
const userIds = [ObjectId("..."), ObjectId("..."), ObjectId("...")]
db.users.find({ "_id": { $in: userIds } })

写入优化

// 批量插入
const documents = []
for (let i = 0; i < 1000; i++) {
  documents.push({
    "name": `User ${i}`,
    "email": `user${i}@example.com`,
    "createdAt": new Date()
  })
}

db.users.insertMany(documents, { ordered: false })

// 批量更新
db.users.bulkWrite([
  {
    updateOne: {
      "filter": { "_id": ObjectId("...") },
      "update": { $set: { "lastLoginAt": new Date() } }
    }
  },
  {
    updateOne: {
      "filter": { "_id": ObjectId("...") },
      "update": { $inc: { "loginCount": 1 } }
    }
  }
], { ordered: false })

// 使用 upsert 避免重复检查
db.userStats.updateOne(
  { "userId": ObjectId("...") },
  {
    $inc: { "pageViews": 1 },
    $setOnInsert: {
      "userId": ObjectId("..."),
      "createdAt": new Date()
    }
  },
  { upsert: true }
)

// 使用 writeConcern 控制写入确认
db.criticalData.insertOne(
  { "data": "important" },
  { writeConcern: { w: "majority", j: true } }
)

内存和存储优化

// 使用紧凑的数据类型
{
  "_id": ObjectId("..."),
  "status": 1,  // 使用数字而不是字符串
  "flags": 0b101010,  // 使用位字段
  "timestamp": NumberLong(1690718400000),  // 使用时间戳而不是 Date
  "coordinates": [37.7749, -122.4194]  // 使用数组而不是对象
}

// 避免深层嵌套
// 不好的设计
{
  "user": {
    "profile": {
      "personal": {
        "address": {
          "home": {
            "street": "123 Main St"
          }
        }
      }
    }
  }
}

// 更好的设计
{
  "user": {
    "homeAddress": "123 Main St"
  }
}

// 使用数组索引而不是对象键
// 不好的设计
{
  "monthlyData": {
    "2024-01": { "sales": 1000 },
    "2024-02": { "sales": 1200 },
    "2024-03": { "sales": 1100 }
  }
}

// 更好的设计
{
  "monthlyData": [
    { "month": "2024-01", "sales": 1000 },
    { "month": "2024-02", "sales": 1200 },
    { "month": "2024-03", "sales": 1100 }
  ]
}

分片和副本集

分片策略

// 启用分片
sh.enableSharding("ecommerce")

// 基于哈希的分片(适合均匀分布)
sh.shardCollection("ecommerce.users", { "_id": "hashed" })

// 基于范围的分片(适合范围查询)
sh.shardCollection("ecommerce.orders", { "customerId": 1, "createdAt": 1 })

// 基于地理位置的分片
sh.shardCollection("ecommerce.stores", { "location": "2dsphere" })

// 复合分片键
sh.shardCollection("ecommerce.products", { "categoryId": 1, "_id": 1 })

// 查看分片状态
sh.status()

// 查看集合分片信息
db.orders.getShardDistribution()

// 平衡分片
sh.enableBalancing("ecommerce.orders")
sh.startBalancer()

副本集配置

// 初始化副本集
rs.initiate({
  "_id": "myReplicaSet",
  "members": [
    { "_id": 0, "host": "mongodb1.example.com:27017", "priority": 2 },
    { "_id": 1, "host": "mongodb2.example.com:27017", "priority": 1 },
    { "_id": 2, "host": "mongodb3.example.com:27017", "priority": 1 },
    { "_id": 3, "host": "mongodb4.example.com:27017", "arbiterOnly": true }
  ]
})

// 添加副本集成员
rs.add("mongodb5.example.com:27017")

// 添加仲裁者
rs.addArb("mongodb6.example.com:27017")

// 设置读取偏好
db.orders.find().readPref("secondary")
db.orders.find().readPref("secondaryPreferred")

// 查看副本集状态
rs.status()

// 强制选举
rs.stepDown()

监控和维护

性能监控

// 数据库统计信息
db.stats()

// 集合统计信息
db.users.stats()

// 索引统计信息
db.users.aggregate([{ $indexStats: {} }])

// 当前操作
db.currentOp({
  "active": true,
  "secs_running": { $gte: 5 }
})

// 服务器状态
db.serverStatus()

// 查看连接信息
db.serverStatus().connections

// 查看内存使用
db.serverStatus().mem

// 查看网络统计
db.serverStatus().network

维护操作

// 重建索引
db.users.reIndex()

// 压缩集合
db.runCommand({ compact: "users" })

// 验证集合
db.users.validate()

// 修复数据库
db.repairDatabase()

// 创建数据库备份
mongodump --db ecommerce --out /backup/

// 恢复数据库
mongorestore --db ecommerce /backup/ecommerce/

// 导出集合
mongoexport --db ecommerce --collection users --out users.json

// 导入集合
mongoimport --db ecommerce --collection users --file users.json

最佳实践总结

1. 数据建模原则

  • 根据查询模式设计:优先考虑应用的查询需求
  • 嵌入 vs 引用:根据数据关系和访问模式选择
  • 避免过度规范化:适度的冗余可以提高性能
  • 考虑文档大小:单个文档不应超过 16MB
  • 使用合适的数据类型:选择最紧凑的数据类型

2. 索引策略

  • 为常用查询创建索引:分析查询模式
  • 复合索引字段顺序:遵循 ESR 规则
  • 定期监控索引使用:删除未使用的索引
  • 避免过多索引:平衡查询性能和写入性能

3. 查询优化

  • 使用投影:只获取需要的字段
  • 限制结果数量:使用 limit() 和分页
  • 使用聚合管道:处理复杂的数据转换
  • 监控慢查询:启用查询分析器

4. 应用架构

  • 连接池管理:合理配置连接池大小
  • 读写分离:使用副本集分担读取负载
  • 缓存策略:在应用层实现缓存
  • 错误处理:实现重试机制和降级策略

5. 运维监控

  • 定期备份:制定备份和恢复策略
  • 性能监控:监控关键指标
  • 容量规划:预估存储和性能需求
  • 安全配置:启用认证和授权

MongoDB 的灵活性使其成为现代应用开发的理想选择,但正确的数据建模和优化策略对于构建高性能、可扩展的应用至关重要。通过遵循这些最佳实践,你可以充分发挥 MongoDB 的优势,构建出色的数据驱动应用。

返回博客列表
感谢阅读!