ARTICLE DETAIL

建站实战干货

来自一线的建站与推广经验沉淀,每一条都经过真实交付验证。

MongoDB实验数据集实战:从导入到聚合查询的完整指南

2026/9/23 10:43:15 拓冰建站 浏览量
MongoDB实验数据集实战:从导入到聚合查询的完整指南 简介这份 MongoDB 实验数据集面向正在学习文档型数据库的初学者及需要快速搭建测试环境的开发者可作为课堂练习或项目验证的基础数据包。压缩包内共两个文件一个 JSON 数据文件用于存储集合内容一个 JS 辅助脚本用于加载或批量处理数据整体只有约三十KB方便直接下载并在本机运行。截至目前已有八百五十七人学习下载适合教学演示和个人自学。通过这套文件用户可实践集合的导入导出、字段的增删改查、条件筛选和聚合分组等常用操作配合 JSON 的嵌套结构还能观察 BSON 的文档模型理解数组、日期与对象等数据类型的存储方式。此外借助脚本对数据做预处理能模拟更接近真实业务的场景为后续学习索引优化、复制集配置和分片集群管理打下扎实基础。1. MongoDB实验数据集到底能拿来练什么一份成绩单数据几乎覆盖日常 80% 操作拿到MongoDB实验数据集这个压缩包时我原本以为里面是一堆杂乱的 JSON 测试文件解压后才发现结构非常干净核心就两个文件load_scores.js和scores_jsonArray.json。这种组织方式对于要练习 MongoDB 基本操作的人来说反而是最舒服的——一个文件教你用 mongo shell 脚本批量写入一个文件教你用mongoimport命令导入 JSON 数组两条路覆盖了 MongoDB 最常见的两种数据灌入方式。如果你刚接触 MongoDB或者已经在用但一直没找到一个像样的数据集来练查询、聚合和索引优化这份资源值得下载。它能让你在半小时内把数据库建起来然后专心研究find()、aggregate()和索引调优而不是把时间浪费在造数据上。2. 导入数据集load_scores.js 和 JSON 数组文件的两种玩法2.1 先用 mongoimport 把 scores_jsonArray.json 灌进去对于大多数场景我建议先走mongoimport这条路。打开终端确认 MongoDB 服务已经启动然后执行mongoimport --db experiment --collection scores --file scores_jsonArray.json --jsonArray --drop这条命令的作用是把scores_jsonArray.json里的 JSON 数组导入到experiment数据库的scores集合中。--jsonArray参数告诉mongoimport这个文件里是一个数组而不是每行一个 JSON 对象少了它基本必报错。--drop表示如果scores集合已存在先删掉再导入避免重复数据把集合搞得一团糟。导入成功后进入 mongo shell 验证一下数据条数mongosh use experiment db.scores.countDocuments() db.scores.findOne()countDocuments()在 MongoDB 4.0 之后的版本里是推荐写法老教程里常见的db.scores.count()在新版 shell 里已经标记为废弃。findOne()用来快速看一下单条文档的字段结构确认字段名和数据类型是否符合预期。这里有个小细节如果你用的是 Robo 3T 或者 MongoDB Compass导入时同样指定 JSON 数组格式Compass 的导入向导里有一个 JSON Array 的勾选项漏勾了也会报 Failed to parse 之类的错。2.2 再走 load() 脚本路线理解 mongo shell 的批量写入逻辑load_scores.js走的是另一条路——在 mongo shell 里直接执行 JavaScript 脚本。这种方式的典型内部结构是一个包含大量文档的数组然后循环或直接调用insertMany()写入// load_scores.js 核心逻辑示意 const scoresData [ { student_id: 1, class_id: 101, score: 85, subject: math }, { student_id: 2, class_id: 102, score: 92, subject: english } ]; db.scores.insertMany(scoresData);在 mongo shell 里加载这个脚本mongosh experiment --file load_scores.js或者先进 shell 再手动执行mongosh use experiment load(load_scores.js)load()是 mongo shell 的内置函数负责把 JavaScript 文件读进来执行。注意路径问题如果脚本不在当前目录要用绝对路径或相对路径否则报 couldnt open file。insertMany()和insert()最大的区别在于批量性能insertMany一次性把整个数组发给服务端而循环调用insert会产生大量网络往返几万条数据的导入时间能差出好几倍。如果实验数据量比较大脚本里用insertMany是更合理的姿势。2.3 两种导入方式怎么选看你的学习目标和数据规模对比维度mongoimportload() 脚本适用场景一次性导入现成数据文件需要自定义逻辑、多次执行数据格式JSON / CSV / TSV需对应参数JS 数组组织方式更灵活调试便利性出错信息比较直接可以在脚本里加日志打印学习价值掌握 mongoimport 参数体系理解 shell 脚本与数据库的交互方式我的建议是两条路都走一遍。先mongoimport把数据灌进去再跑load_scores.js对比两者导入结果是否一致。这个对比本身就是一次很好的实验——能验证两套导入逻辑用的是同样的字段结构也能顺便检查数据里有没有被重复写入。3. 查询与聚合实操把成绩单数据玩出花来3.1 find() 的基本查询筛选、排序、投影的完整套路数据导入之后第一件正经事就是练find()。不要小看这个操作很多人在实际项目里只会find({})一遇到复杂条件就开始翻文档。这里给出一组能覆盖日常 80% 查询需求的写法// 查询数学成绩大于等于90分的学生 db.scores.find( { subject: math, score: { $gte: 90 } }, { student_id: 1, score: 1, _id: 0 } ).sort({ score: -1 }).limit(10)这个查询里有几个关键点。第一个参数是查询条件$gte是 MongoDB 的比较操作符表示大于等于类似的还有$gt、$lt、$lte、$ne第二个参数是投影只返回student_id和score两个字段_id: 0表示不返回默认的_id字段。sort({ score: -1 })按成绩倒序排列limit(10)只取前 10 条。如果你想知道每个班级有多少人参加了考试可以配合countDocuments()加条件db.scores.countDocuments({ class_id: 101 })这里有个实战中很容易翻车的点直接用db.scores.find({}, {_id: 0}).count()在旧版 shell 里能用但新版 mongosh 里对 find 结果调用 count 会有兼容性问题统一用countDocuments()最稳妥。3.2 aggregate() 聚合管道分组统计和趋势分析的核心武器单表查询只是热身MongoDB 真正值钱的能力在聚合管道。这份实验数据里有subject、score、class_id字段非常适合练$group、$project、$sort的组合拳。下面这个例子统计每个科目的平均分、最高分和最低分db.scores.aggregate([ { $group: { _id: $subject, avgScore: { $avg: $score }, maxScore: { $max: $score }, minScore: { $min: $score }, examCount: { $sum: 1 } }}, { $sort: { avgScore: -1 } }, { $project: { subject: $_id, avgScore: 1, maxScore: 1, minScore: 1, examCount: 1, _id: 0 }} ])$group是整个管道的心脏_id指定分组依据后面的$avg、$max、$min都是累加器操作符$sum: 1表示每匹配一条文档就加 1用来计数。$sort按平均分倒序排列。最后的$project做字段映射把_id重命名为subject同时隐藏默认的_id。如果你想看每个班级每个科目的成绩分布可以在$group里用复合分组db.scores.aggregate([ { $group: { _id: { class: $class_id, subject: $subject }, avgScore: { $avg: $score }, studentCount: { $sum: 1 } }}, { $sort: { avgScore: -1 } } ])多个字段分组时_id可以是一个嵌套对象。这个写法的输出结果里_id.class和_id.subject会作为分组标识后续如果要继续处理管道结果可以用$_id.class来引用。3.3 $match 和 $project 的提前过滤先瘦身再计算聚合管道有一个常见性能误区把大量数据全部灌进$group之后再过滤。正确的姿势是先用$match把不相关的文档挡在管道外面db.scores.aggregate([ { $match: { subject: math, score: { $gte: 60 } } }, { $group: { _id: $class_id, passRate: { $avg: { $cond: [{ $gte: [$score, 60] }, 1, 0] } }, total: { $sum: 1 } }}, { $sort: { passRate: -1 } } ])这里$match先把非数学科目和不及格的数据排除掉$cond是条件表达式如果分数大于等于 60 返回 1否则返回 0$avg算出来的就是一个班级的及格率。这个管道的执行顺序是过滤、分组、排序每一步的数据量都在减小整体执行效率比先$group再$match高出一个量级。实际做实验时你可以在$match之前和之后分别执行一次explain()对比 stage 的执行统计这是 MongoDB 查询优化的基本功。4. 避坑指南导入和数据操作的四个经典翻车现场4.1 mongoimport 报错 Failed to parseJSON 数组没有加 --jsonArray现象执行mongoimport --db experiment --collection scores --file scores_jsonArray.json时报错提示无法解析 JSON 内容。原因scores_jsonArray.json是一个 JSON 数组mongoimport默认按每行一个 JSON 对象的格式解析遇到数组开头的[直接懵掉。解决加上--jsonArray参数明确告诉导入工具文件里是数组格式。如果文件很大或者网络不稳定可以加--batchSize 1000分批写入避免一次性提交海量数据导致内存溢出。4.2 load() 脚本执行时 ReferenceError: db is not defined现象在 mongosh 里直接运行load(load_scores.js)脚本内部使用了db.scores.insertMany(...)报错说 db 未定义或者提示 Buffer 拼写问题。原因新版 mongosh 对 JavaScript 运行环境和旧版 mongo shell 有差异部分脚本里用了全局变量db但加载时机不对或者用了旧 shell 才支持的 API。解决先执行use experiment切换到目标数据库再执行load()。如果脚本里用到了Buffer等 Node.js 全局对象说明这个脚本可能是为旧版 mongo shell 写的可以在文件开头加const db db.getSiblingDB(experiment)显式声明数据库引用或者在 mongosh 命令行里用--eval配合load()传参。4.3 数字被识别成字符串排序和聚合结果全乱现象导入之后执行db.scores.find().sort({ score: -1 })发现排序结果不对最大值不是数字 100而是字符串 100聚合算平均值时报$avg只支持数字类型。原因原始 JSON 文件里某个字段的值为85带引号mongoimport严格按照 JSON 字面量解析带引号的数字就是字符串。解决写一个脚本批量修正字段类型db.scores.find({ score: { $type: string } }).forEach(function(doc) { doc.score parseInt(doc.score); db.scores.replaceOne({ _id: doc._id }, doc); });$type: string筛选出所有 score 字段为字符串的文档parseInt转成数字后用replaceOne写回。这个操作务必在导入后立刻做否则后续所有分析和索引优化都会建立在错误的数据类型上。4.4 ObjectId 重复导致 update 翻车更新语句不可见现象执行db.scores.update({ student_id: 1 }, { $set: { score: 99 } })之后查询发现有的文档更新了有的没有甚至报_id重复错误。原因load_scores.js脚本里如果手动指定了_id字段或者在多次执行load()时没清空集合数据会重复导入相同的_id就会冲突。MongoDB 默认不强制_id唯一性之外的约束重复导入时部分文档会被覆盖或者写入失败。解决在load()之前先执行db.scores.deleteMany({})或者用--drop参数重建集合。如果是脚本里手动指定了_id建议去掉_id字段让 MongoDB 自动生成 ObjectId这样后续文档结构更干净_id也天然带时间信息。5. 进阶玩法用 ObjectId 做时间维度的抽查以及把聚合结果导出回流5.1 ObjectId 自带时间戳几行代码就能做数据新鲜度抽样MongoDB 的 ObjectId 前 4 个字节存储的是创建时间戳Unix 时间这意味着你不需要额外维护创建时间字段就能知道一条文档是什么时候写入的。对于这份实验数据如果你想验证两次导入是否产生了重复记录或者想按时间维度抽样检查数据覆盖情况可以用getTimestamp()方法db.scores.find().limit(5).forEach(function(doc) { print(文档 _id: doc._id 写入时间: doc._id.getTimestamp()); });getTimestamp()返回一个 Date 对象可以直接用toISOString()格式化输出。这个技巧在排查数据导入问题时非常实用——如果你发现导入的数据量翻倍但countDocuments()结果正常八成是脚本里用了upsert导致部分文档被覆盖用_id.getTimestamp()对比前后两次写入的时间范围就能快速定位。5.2 把聚合结果导出成 JSON回流给脚本做断言验证聚合管道的结果可以直接导出到文件用来做二次分析或者在脚本里做自动化验证。mongo shell 里的toArray()配合printjsononeline可以把聚合结果变成一行一个 JSON 对象mongosh experiment --quiet --eval const result db.scores.aggregate([ { \$group: { _id: \$subject, avg: { \$avg: \$score } } } ]).toArray(); result.forEach(doc print(JSON.stringify(doc))); aggregation_result.json注意在 bash 命令行里用--eval时聚合管道里的$符号要转义成\$否则 shell 会尝试做变量替换。--quiet参数抑制掉 shell 的启动横幅保证输出文件里只有纯 JSON 数据。导出之后你可以在 Node.js 或者 Python 脚本里读取这个文件写断言验证查询逻辑是否正确import json with open(aggregation_result.json) as f: lines f.readlines() results [json.loads(line) for line in lines if line.strip()] for item in results: assert item[avg] 0, f科目 {item[_id]} 的平均分异常 print(f科目 {item[_id]} 平均值: {item[avg]})这套导入 → 聚合 → 导出 → 断言的闭环本质上就是你在真实项目里做数据管道验证的缩小版。我后来每次拿到一个新的数据集都会强制走一遍这个流程——先mongoimport灌数据再用_id.getTimestamp()确认数据写入时间范围接着跑一轮聚合导出 JSON最后写脚本断言关键指标。整个过程不到十分钟但能挡住后续一长串因为数据类型、字段命名和导入缺失引发的玄学问题。希望这份笔记能帮你在 MongoDB 实验数据集上少走几步弯路。本文还有配套的精品资源点击获取