MongoDB 作为非关系型数据库,虽无 “表”“SQL” 概念,但慢查询的核心诱因(无索引、全集合扫描、复杂聚合)与 MySQL 类似。以下参考 MySQL 慢查询模拟逻辑,从 “环境准备 - 数据构造 - 慢查询触发 - 排查 - 优化” 全流程,以 “用户集合(user)” 为核心场景展开,确保操作可复现。
一、前期准备:MongoDB 环境与工具搭建(类比 MySQL 环境)
- 环境部署
本地部署:下载 MongoDB 社区版(5.0 + 版本),解压后执行mongod --dbpath 数据存储路径启动服务,默认端口 27017;
Docker 部署(更便捷):
连接验证:使用 MongoDB 自带客户端mongo(或可视化工具 Robo 3T、Compass)连接,执行show dbs确认服务正常。 - 工具准备
客户端工具:Robo 3T(可视化操作集合、执行查询)、MongoDB Compass(自带性能分析功能);
慢查询分析工具:MongoDB 内置的db.currentOp()(查看当前运行操作)、system.profile(慢查询日志集合)。
二、第一步:构造用户集合与大量模拟数据(类比 MySQL 建表 + 插数据) - 创建数据库与集合(类比 MySQL 建库建表)
MongoDB 无需提前创建数据库(插入数据时自动创建),直接创建 “用户集合(user)”,字段设计参考 MySQL 用户表,贴合业务查询场景:
- 生成 100 万条模拟数据(类比 MySQL 存储过程插数据)
通过 MongoDB 的bulkWrite批量插入(效率高于单条插入),生成符合业务分布的数据(如 city 覆盖 30 个城市、age 18-60 岁、createTime 近 2 年):
三、第二步:执行查询触发慢查询(类比 MySQL 低效 SQL)
MongoDB 慢查询的核心诱因是 “全集合扫描(类似 MySQL 全表扫描)”“无索引复杂聚合”,设计 4 类典型低效查询,触发慢查询(默认慢查询阈值为 100ms,超过即视为慢查询)。 - 场景 1:无索引单条件过滤(类比 MySQL 无索引单表查询)
查询 “北京市用户数量”,city字段无索引,触发全集合扫描:
- 场景 2:无索引多条件组合查询(类比 MySQL 多条件无索引)
查询 “2024 年注册、年龄 25-30 岁的女性用户列表”,createTime“age”“gender” 均无索引:
- 场景 3:索引失效(类比 MySQL 函数导致索引失效)
先为createTime创建索引,再用$expr函数操作字段,导致索引失效:
// 1. 为createTime创建单字段索引
db.user.createIndex({ createTime: 1 });
print("已创建createTime索引");
// 2. 用$expr函数操作createTime,导致索引失效
const start3 = Date.now();
const june2024Users = db.user.countDocuments({
$expr: { $eq: [{ $dateToString: { format: "%Y-%m", date: "$createTime" } }, "2024-06"] }
});
const cost3 = Date.now() - start3;
print(函数操作索引字段查询2024年6月用户数:${june2024Users},耗时:${cost3}ms
);
// 预期结果:耗时约600-2500ms(索引失效,全集合扫描)
- 场景 4:无索引聚合排序(类比 MySQL 无索引 GROUP BY+ORDER BY)
查询 “各城市用户数量 TOP10”,city无索引,聚合操作需全集合扫描后排序:
const start4 = Date.now();
// 无索引聚合排序
const cityUserTop10 = db.user.aggregate([
{ $group: { _id: "$city", userCount: { $sum: 1 } } }, // 按city分组统计
{ $sort: { userCount: -1 } }, // 按用户数降序
{ $limit: 10 } // 取TOP10
]).toArray();
const cost4 = Date.now() - start4;
print(无索引聚合城市用户TOP10耗时:${cost4}ms
);
// 预期结果:耗时约1000-3500ms(全集合扫描+分组+排序)
四、第三步:慢查询排查(类比 MySQL 慢查询日志 + EXPLAIN)
MongoDB 通过 “慢查询日志(system.profile)” 和 “执行计划(explain)” 排查慢查询,核心逻辑与 MySQL 类似。 - 启用慢查询日志(类比 MySQL slow_query_log)
MongoDB 通过db.setProfilingLevel()启用慢查询记录,记录存储在system.profile集合中:
// 1. 启用慢查询日志:level=1表示记录超过threshold(毫秒)的查询,这里设为100ms
db.setProfilingLevel(1, { slowms: 100 });
print("已启用慢查询日志,记录耗时>100ms的操作");
// 2. 查看慢查询记录(筛选user集合的慢查询)
const slowQueries = db.system.profile.find({
ns: "slow_query_test.user", // 命名空间(数据库.集合)
op: { $in: ["query", "count", "aggregate"] } // 操作类型(查询、计数、聚合)
}).sort({ ts: -1 }).limit(10).toArray();
// 打印慢查询详情(重点看durationMillis、planSummary)
slowQueries.forEach((query, index) => {
print(\n慢查询${index+1}:
);
print(操作类型:${query.op}
);
print(耗时:${query.durationMillis}ms
);
print(查询条件:${JSON.stringify(query.query)}
);
print(执行计划概要:${query.planSummary}
); // 全集合扫描会显示COLLSCAN
});
- 查看执行计划(类比 MySQL EXPLAIN)
通过explain()分析查询的执行计划,判断是否全集合扫描、是否使用索引:
// 分析场景1(无索引查北京用户)的执行计划
const explain1 = db.user.explain("executionStats").countDocuments({ city: "北京" });
print("\n场景1执行计划:");
print(扫描类型:${explain1.executionStats.executionStages.stage}
); // COLLSCAN=全集合扫描
print(扫描文档数:${explain1.executionStats.totalDocsExamined}
); // 1000000(全量扫描)
print(返回结果数:${explain1.executionStats.nReturned}
);
// 分析场景3(索引失效)的执行计划
const explain3 = db.user.explain("executionStats").countDocuments({
$expr: { $eq: [{ $dateToString: { format: "%Y-%m", date: "$createTime" } }, "2024-06"] }
});
print("\n场景3执行计划:");
print(扫描类型:${explain3.executionStats.executionStages.stage}
); // 仍为COLLSCAN(索引失效)
print(是否使用索引:${explain3.queryPlanner.winningPlan.inputStage.stage === "IXSCAN"}
); // false
五、第四步:慢查询优化(类比 MySQL 索引 + SQL 优化)
MongoDB 优化核心仍是 “索引优化”+“查询逻辑优化”,针对上述场景逐一优化: - 场景 1 优化:为 city 创建单字段索引
// 创建city索引
db.user.createIndex({ city: 1 });
print("已创建city索引");
// 重新执行查询,验证优化效果
const start1Opt = Date.now();
const beijingUserCountOpt = db.user.countDocuments({ city: "北京" });
const cost1Opt = Date.now() - start1Opt;
print(索引优化后查询北京用户数:${beijingUserCountOpt},耗时:${cost1Opt}ms
);
// 预期结果:耗时降至10-50ms(索引扫描IXSCAN)
- 场景 2 优化:为多条件创建复合索引(类比 MySQL 联合索引)
按 “过滤频率高→范围字段” 顺序创建复合索引(gender>age>createTime):
db.user.createIndex({ gender: 1, age: 1, createTime: 1 });
print("已创建gender+age+createTime复合索引");
// 重新执行多条件查询
const start2Opt = Date.now();
const userListOpt = db.user.find({
gender: 2,
age: { $gte: 25, $lte: 30 },
createTime: { $gte: new Date("2024-01-01"), $lte: new Date("2024-12-31") }
}).project({ userName: 1, phone: 1, _id: 0 }).toArray();
const cost2Opt = Date.now() - start2Opt;
print(复合索引优化后多条件查询结果数:${userListOpt.length},耗时:${cost2Opt}ms
);
// 预期结果:耗时降至15-80ms
- 场景 3 优化:避免函数操作,使用索引范围查询
修改查询逻辑,用$gte/$lt替代$expr函数,复用createTime索引:
// 优化后查询(无函数操作,使用索引)
const start3Opt = Date.now();
const june2024UsersOpt = db.user.countDocuments({
createTime: {
$gte: new Date("2024-06-01"),
$lt: new Date("2024-07-01") // 左闭右开,避免漏数据
}
});
const cost3Opt = Date.now() - start3Opt;
print(优化后查询2024年6月用户数:${june2024UsersOpt},耗时:${cost3Opt}ms
);
// 预期结果:耗时降至5-30ms(索引扫描IXSCAN)
- 场景 4 优化:为聚合分组字段创建索引
复用场景 1 的city索引,聚合操作可利用索引避免全集合扫描:
// 重新执行聚合查询(复用city索引)
const start4Opt = Date.now();
const cityUserTop10Opt = db.user.aggregate([
{ $group: { _id: "$city", userCount: { $sum: 1 } } },
{ $sort: { userCount: -1 } },
{ $limit: 10 }
]).toArray();
const cost4Opt = Date.now() - start4Opt;
print(索引优化后聚合城市用户TOP10耗时:${cost4Opt}ms
);
// 预期结果:耗时降至20-100ms
六、总结:MongoDB 与 MySQL 慢查询逻辑对比
环节
MySQL
MongoDB
核心共性
数据存储
表(Table)+ 行(Row)
集合(Collection)+ 文档(Document)
均需构造大量数据触发慢查询
慢查询诱因
全表扫描、无索引关联、函数失效
全集合扫描(COLLSCAN)、无索引聚合
核心是 “无索引导致扫描量过大”
排查工具
slow_query_log、EXPLAIN
system.profile、explain()
均通过日志 + 执行计划定位问题
优化核心
单表 / 联合索引、SQL 逻辑优化
单字段 / 复合索引、查询逻辑优化
均以 “索引优化” 为核心
通过类比 MySQL 逻辑,可快速掌握 MongoDB 慢查询的模拟与优化思路,核心是理解 “全集合扫描” 的危害,通过合理设计索引减少数据扫描量,提升查询效率。