<返回目录     Powered by claud/xia兄

第14课: 性能优化

explain() - 查询分析

// 查看查询执行计划
db.users.find({ age: 25 }).explain("executionStats")

// 三种模式:
// - queryPlanner: 只显示查询计划
// - executionStats: 显示执行统计(推荐)
// - allPlansExecution: 显示所有候选计划

关键性能指标

// 分析explain()输出
{
    "executionStats": {
        "executionTimeMillis": 15,      // 执行时间(毫秒)
        "totalDocsExamined": 10000,     // 扫描的文档数
        "totalKeysExamined": 10000,     // 扫描的索引键数
        "nReturned": 50,                // 返回的文档数
        "executionStages": {
            "stage": "IXSCAN",          // IXSCAN=索引扫描,COLLSCAN=全表扫描
            "indexName": "age_1"
        }
    }
}

// 理想情况:
// - totalKeysExamined ≈ nReturned
// - stage = "IXSCAN"
// - executionTimeMillis < 100ms

索引优化

// 1. 为查询字段创建索引
db.users.createIndex({ email: 1 })

// 2. 复合索引遵循ESR规则
// E (Equality): 等值查询字段
// S (Sort): 排序字段
// R (Range): 范围查询字段
db.orders.createIndex({ status: 1, createdAt: -1, amount: 1 })

// 3. 覆盖索引(Covered Query)
// 查询的所有字段都在索引中
db.users.find(
    { age: 25 },
    { age: 1, name: 1, _id: 0 }
).hint({ age: 1, name: 1 })

// 4. 删除未使用的索引
db.users.dropIndex("unused_index_1")

查询优化技巧

// 1. 使用投影减少数据传输
db.users.find({}, { name: 1, email: 1 })

// 2. 限制返回数量
db.users.find().limit(100)

// 3. 使用$exists检查字段存在性
db.users.find({ email: { $exists: true } })

// 4. 避免$where和$regex(性能差)
// 差:
db.users.find({ $where: "this.age > 18" })
// 好:
db.users.find({ age: { $gt: 18 } })

// 5. 使用$in代替多个$or
// 差:
db.users.find({ $or: [{ age: 20 }, { age: 25 }, { age: 30 }] })
// 好:
db.users.find({ age: { $in: [20, 25, 30] } })

慢查询日志

// 启用慢查询日志(超过100ms)
db.setProfilingLevel(1, { slowms: 100 })

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

// 分析慢查询
db.system.profile.find({
    millis: { $gt: 100 }
}).sort({ millis: -1 })

// 关闭慢查询日志
db.setProfilingLevel(0)

聚合优化

// 1. 尽早使用$match过滤
db.orders.aggregate([
    { $match: { status: "completed" } },  // 先过滤
    { $group: { _id: "$userId", total: { $sum: "$amount" } } }
])

// 2. 尽早使用$project减少字段
db.orders.aggregate([
    { $project: { userId: 1, amount: 1 } },  // 只保留需要的字段
    { $group: { _id: "$userId", total: { $sum: "$amount" } } }
])

// 3. 使用$limit减少处理数据量
db.orders.aggregate([
    { $match: { status: "completed" } },
    { $sort: { amount: -1 } },
    { $limit: 100 }
])

// 4. 使用allowDiskUse处理大数据集
db.orders.aggregate(
    [...],
    { allowDiskUse: true }
)

连接池优化

// Node.js驱动配置
const client = new MongoClient(uri, {
    maxPoolSize: 50,        // 最大连接数
    minPoolSize: 10,        // 最小连接数
    maxIdleTimeMS: 30000,   // 空闲连接超时
    waitQueueTimeoutMS: 5000 // 等待连接超时
});

批量操作优化

// 使用bulkWrite批量写入
db.users.bulkWrite([
    { insertOne: { document: { name: "用户1" } } },
    { updateOne: { filter: { _id: 1 }, update: { $set: { age: 26 } } } },
    { deleteOne: { filter: { _id: 2 } } }
], { ordered: false })  // 无序执行更快

// 批量插入
db.users.insertMany(
    [...1000个文档...],
    { ordered: false }
)

内存优化

// 1. 工作集(Working Set)应该小于RAM
// 查看工作集大小
db.serverStatus().wiredTiger.cache

// 2. 配置WiredTiger缓存大小
// 默认:(RAM - 1GB) / 2 或 256MB(取较大值)
mongod --wiredTigerCacheSizeGB 4

// 3. 监控内存使用
db.serverStatus().mem

磁盘I/O优化

// 1. 使用SSD存储
// 2. 分离数据和日志到不同磁盘
mongod --dbpath /data/db --logpath /logs/mongodb.log

// 3. 启用压缩
mongod --wiredTigerCollectionBlockCompressor snappy

// 4. 调整日志提交间隔
mongod --wiredTigerJournalCompressor snappy

读写分离

// 将读操作分散到从节点
db.users.find().readPref("secondaryPreferred")

// 适用场景:
// - 读多写少
// - 可以容忍轻微的数据延迟
// - 减轻主节点压力

数据建模优化

// 1. 嵌入式文档减少关联查询
{
    orderId: "order123",
    user: {
        id: "user123",
        name: "张三",
        email: "zhangsan@example.com"
    },
    items: [...]
}

// 2. 使用桶模式聚合时序数据
{
    sensorId: "sensor001",
    hour: ISODate("2026-02-03T10:00:00Z"),
    measurements: [
        { minute: 0, temp: 25.5 },
        { minute: 1, temp: 25.6 }
        // ... 60条数据
    ]
}

// 3. 使用属性模式处理稀疏字段
{
    productId: "prod123",
    attributes: [
        { key: "color", value: "red" },
        { key: "size", value: "L" }
    ]
}

监控工具

// 1. mongostat - 实时统计
mongostat --host localhost:27017

// 2. mongotop - 查看集合读写时间
mongotop --host localhost:27017

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

// 4. 终止慢查询
db.killOp(opid)

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

// 6. 数据库统计
db.stats()

// 7. 集合统计
db.users.stats()

性能测试

// 使用mongoperf测试磁盘性能
mongoperf < test.json

// test.json内容
{
    nThreads: 16,
    fileSizeMB: 1000,
    sleepMicros: 0,
    mmf: true,
    r: true,
    w: true
}

常见性能问题

问题 原因 解决方案
查询慢 缺少索引 创建合适的索引
写入慢 索引过多 删除不必要的索引
内存不足 工作集过大 增加RAM或分片
磁盘I/O高 频繁读写 使用SSD,优化查询
连接数过多 连接池配置不当 调整连接池大小

性能优化清单

性能优化原则:
练习题:
  1. 使用explain()分析一个慢查询并优化
  2. 启用慢查询日志并分析最慢的10个查询
  3. 为高频查询创建覆盖索引
  4. 优化一个聚合管道的性能
  5. 使用mongostat监控数据库性能
  6. 实现批量写入优化