MongoDB实验数据集实操:从mongoimport导入到聚合查询与索引调优

发布时间:2026/10/4 7:46:23

MongoDB实验数据集实操:从mongoimport导入到聚合查询与索引调优 简介这是一套面向MongoDB学习者的实验数据集主题聚焦成绩数据的导入、查询与聚合分析适合入门开发者练习基础操作、熟悉文档型数据库特性。压缩包共2个文件包含一个JSON格式的数据集合文件与一个JS辅助脚本整体仅30KB轻量易用便于快速加载到本地MongoDB实例中。已有857人学习下载可作为课堂配套或自学演练素材。通过这份数据读者可实践mongoimport导入流程理解BSON文档与集合结构结合脚本尝试find条件筛选、聚合管道分组统计、更新与删除操作并进一步体会索引对查询性能的影响。虽是小型数据集但覆盖了从基础增删改查到分布式集群演示的常见实验场景能为后续学习复制集、分片等高级内容打下操作基础。1. 为什么这份 MongoDB 实验数据集值得导入前先拆一遍做 MongoDB 实验最头疼的不是语法记不住而是手头没有一份像样的数据。MongoDB实验数据集这个压缩包解出来是load_scores.js和scores_jsonArray.json从文件名看就是一个用于练习查询与删除的 scores 成绩集合JSON 数组格式存放脚本负责把数据批量灌进库。它解决的问题很具体你不用再为find()、aggregate()、updateMany()现造几十条假记录导入即用造出来的数据形态还贴近真实学生成绩场景。适合两类人一类是准备 MongoDB 认证或课程考核、需要真实结构数据的初学者另一类是要快速验证聚合管道与索引效果的在职工程师。别小看这两个文件把它们跑通后你可以从基础查询一路做到 explain 性能调优基本覆盖日常开发里 80% 的 MongoDB 操作场景。2. 把 scores 数据装进 MongoDBmongoimport、load 脚本与两条导入路线2.1 解开数据资源两个文件的分工与 scores 集合的数据形态解压数据资源.rar后里面是一个“数据资源”目录核心文件就两个scores_jsonArray.json和load_scores.js。前者是数据本体后者是装载脚本。两个文件的实际作用如下表文件形态作用scores_jsonArray.jsonJSON 数组存放成绩集合的原始数据供mongoimport或脚本读取load_scores.jsJS 脚本在 mongo shell / mongosh 中执行把 JSON 数据写入集合这类实验数据集里scores 集合最常见的结构是学生、班级、成绩数组三项student_id表示学生编号class_id表示班级编号scores数组里的每个元素是{ type: exam, score: 80.1 }这种格式type区分 exam、quiz、homework 等题型。为什么用数组包对象因为 scores 的形态恰好把 MongoDB 文档模型的优势暴露出来——同一条学生记录里放一个数组查询用点路径聚合用$unwind不需要像关系型数据库那样拆三张表。[ { student_id: 0, class_id: 1, scores: [ { type: exam, score: 80.1 }, { type: quiz, score: 70.3 } ] }, { student_id: 1, class_id: 2, scores: [ { type: exam, score: 91.5 }, { type: quiz, score: 66 } ] } ]上面只是这类 scores 数据的典型形态你解压后先用编辑器打开scores_jsonArray.json看一眼字段名后文所有示例都按student_id、class_id、scores这套字段来写。如果你的文件字段名不一样替换对应字段即可。2.2 mongoimport 导入 JSON 数组--jsonArray 与显式类型mongoimport是 MongoDB 自带的导入工具处理 JSON 数组格式时有个容易翻车的参数--jsonArray。如果文件整体是一个数组[{...}, {...}]不加这个参数mongoimport会把每一行当成一个独立的 JSON 对象去解析遇到数组开头的[直接解析失败或者只导入了极少数文档。# 推荐先删后导保证重复实验不会追加出重复数据 mongoimport \ --db course \ --collection scores \ --file scores_jsonArray.json \ --jsonArray \ --drop --quiet参数说明--db指定目标数据库数据库不存在时会自动创建--collection指定集合名--file指向 JSON 文件路径--jsonArray告诉工具整个文件是一个数组逐个元素导入--drop表示导入前先清空同名集合反复实验时非常重要否则每次都会追加一份数据。--quiet只是减少输出可加可不加。新版mongoimport还支持直接传连接串效果等价mongoimport \ --uri mongodb://127.0.0.1:27017/course \ --collection scores \ --file scores_jsonArray.json \ --jsonArray --drop导入完成后终端一般会打印imported N documents这个 N 应该和 JSON 数组里的元素个数一致。如果数量对不上先检查文件末尾是否有空行或格式化问题。导入时还有一个容易被忽略的点JSON 文本里的_id字段如果是普通字符串导入后类型就是字符串如果文件里没有_idmongoimport会自动生成ObjectId。这直接影响后面聚合操作对_id的使用方式。2.3 另一条路线直接用 load_scores.js 把数据跑进去load_scores.js是给 mongo shell 准备的导入脚本。这类脚本的典型逻辑是读取同目录的 JSON 文件解析成数组然后清空集合、批量插入。常见结构如下// load_scores.js 的典型写法cat 读取文件 JSON.parse 解析 insertMany 批量写入 db.scores.drop(); var data JSON.parse(cat(scores_jsonArray.json)); db.scores.insertMany(data); print(loaded data.length documents);逻辑说明cat()是 mongo shell 和 mongosh 都提供的全局函数读取文件内容JSON.parse()把 JSON 数组字符串解析成 JS 数组insertMany()一次写入全部文档。脚本里先drop()清空集合是为了保证重复执行时数据不叠加。在老版本 MongoDB 环境里执行方式是mongo course load_scores.js。但 MongoDB 6.0 开始不再自带老版mongoshell统一换成mongosh所以现在的常规做法是cd /path/to/数据资源 mongosh course load_scores.js注意必须先cd到脚本所在目录。脚本里cat(scores_jsonArray.json)用的是相对路径你在别的目录执行它会报找不到文件这属于高频踩坑点后面排查章再细说。执行成功后会打印loaded xxx documents。2.4 导入后的三分钟验证数量、样本与索引状态数据导入完先别急着写查询花三分钟确认三件事文档数量对不对、样本数据结构是否符合预期、集合上现在有哪些索引。# 统计文档数量并打印一条样本 mongosh course --quiet --eval db.scores.countDocuments(); db.scores.findOne() # 查看集合现有的索引 mongosh course --quiet --eval db.scores.getIndexes()countDocuments()返回导入的文档总数拿它和文件里的数组元素数对一下findOne()输出一条完整文档确认scores数组的嵌套结构没有被破坏getIndexes()会显示当前索引列表默认只有_id索引。如果你用的是 MongoDB Compass 这类图形工具也可以直接在 UI 里看集合名和数据预览但我习惯先用命令行确认因为导入是否成功、类型是否异常命令行反馈最直接。3. 文档数据在 MongoDB 中的查询、聚合、删除一个 scores 集合练到底如果是在实训平台上做“本关任务文档数据在 MongoDB 中的查询和删除”这类实验scores 集合导入完成后要练习的操作基本就是三块find()查询、aggregate()聚合、update/delete更新删除。这一章把三个方向完整跑一遍代码在 mongosh 里可以直接执行。3.1 基础查询点路径过滤数组内嵌字段排序 分页控制结果集先做最简单的精确查询按学生编号找人// 查询 student_id 为 5 的学生的全部成绩记录 db.scores.find({ student_id: 5 });逻辑说明find()第一个参数是过滤条件{ student_id: 5 }表示精确匹配。返回值是游标mongosh 会自动打印前 20 条。接着查所有考试成绩大于 90 分的记录并按学生编号升序排列db.scores.find( { scores.score: { $gt: 90 } } ).sort({ student_id: 1 }).limit(20);这里的关键是点路径scores.score。MongoDB 会自动从scores数组的每个元素里取score字段做条件匹配不需要先展开数组。$gt是比较操作符表示大于sort({ student_id: 1 })的1表示升序-1是降序limit(20)限制返回条数。如果想做分页再加.skip(40)跳过前 40 条但skip大偏移量性能会明显下降生产环境分页应该用游标或范围查询这里实验数据量小不用太在意。数组内嵌对象还有个容易写错的场景同时约束某个元素的两个字段。比如查 exam 成绩不低于 90 的记录不能直接写两个点路径条件因为两个条件可能落在数组的不同元素上。要用$elemMatch// 同一个数组元素里type 必须是 exam 且 score 90 db.scores.find({ scores: { $elemMatch: { type: exam, score: { $gte: 90 } } } });说明$elemMatch要求数组里至少有一个元素同时满足type: exam和score 90这两个条件这样结果才有意义。查询结果量大的时候还可以加投影字段减少网络传输db.scores.find( { scores: { $elemMatch: { type: exam, score: { $gte: 90 } } } }, { student_id: 1, class_id: 1, scores.$: 1 } );第二个参数{ student_id: 1, class_id: 1, scores.$: 1 }是投影1表示返回该字段scores.$只返回数组中匹配到的第一个元素。这个技巧在处理大文档时很实用能明显减少返回体量。3.2 聚合管道$unwind 拆数组、$group 算均分、$sort 排名聚合是 MongoDB 实验里最值得花时间的部分。先算每个学生的平均分并按平均分倒序取前五db.scores.aggregate([ { $unwind: $scores }, { $group: { _id: $student_id, avg_score: { $avg: $scores.score } } }, { $sort: { avg_score: -1 } }, { $limit: 5 } ]);逻辑说明$unwind: $scores把每个文档里嵌套的scores数组拆成多行每个元素单独成一条$group按student_id分组$avg对每组的scores.score求平均值$sort按平均分降序排$limit只保留前五。管道顺序很重要展开、分组、排序、截断顺序错了结果完全不一样。再来看一个更接近业务需求的例子统计每个班级、每种题型的平均分和最高分。db.scores.aggregate([ { $unwind: $scores }, { $group: { _id: { class_id: $class_id, type: $scores.type }, avg_score: { $avg: $scores.score }, max_score: { $max: $scores.score } } }, { $sort: { _id.class_id: 1, avg_score: -1 } } ]);这里_id是一个组合键{ class_id, type }MongoDB 会按这两个字段一起分组。注意后面$sort引用组合键字段时要用字符串点路径_id.class_id直接写{ _id.class_id: 1 }语法是错的。$max和$avg一样只对数值字段生效。聚合性能有个习惯要养成能先过滤就先过滤。比如只关心 1 班的数据把$match放在最前面提前减少进入管道的文档量db.scores.aggregate([ { $match: { class_id: 1 } }, { $unwind: $scores }, { $group: { _id: $scores.type, avg_score: { $avg: $scores.score } } } ]);逻辑说明$match相当于查询条件放在管道第一阶段时 MongoDB 会尽量利用索引放后面则只能扫全量内存结果。实验数据量小看不出差别但养成“先过滤后展开”的写法到千万级集合上能省下大量时间。3.3 更新与删除$ 位置操作符、批量更新、dangerous 的 drop更新操作里最容易出错的是嵌套数组字段的定位。先看单条更新把学生 0 的 exam 成绩改成 88。// $ 位置操作符定位查询条件命中的第一个数组元素 db.scores.updateOne( { student_id: 0, scores.type: exam }, { $set: { scores.$.score: 88 } } );参数说明第一个参数是查询条件第二个参数是更新语句。scores.type: exam先命中该学生数组里的 exam 元素$set里的scores.$.score中$就是被命中元素的位置索引只更新这一项。批量更新更复杂一点把所有学生的 quiz 成绩统一加 2 分。直接用$不行因为$只能定位一个元素这里要用数组过滤器$[]配合arrayFiltersdb.scores.updateMany( { scores.type: quiz }, { $inc: { scores.$[elem].score: 2 } }, { arrayFilters: [{ elem.type: quiz }] } );逻辑说明第三个参数里的arrayFilters定义了变量elem的约束条件只有scores数组中type为quiz的元素才会被$inc加 2 分。如果漏了arrayFilters$[elem]没有定义MongoDB 会直接报语法错误。这里也提醒一点很多老教材还在写db.scores.update({...}, {...}, { multi: true })这个三参数的旧式写法在 mongosh 里已经不推荐统一改成updateOne/updateMany语义更清晰。删除操作先确认影响范围再动手// 删除前先数一下确认要删多少 db.scores.countDocuments({ class_id: 1 }); // 按条件删除 db.scores.deleteMany({ class_id: 1 }); // 整个集合删除谨慎 db.scores.drop();提示deleteMany({})会清空集合但保留集合本身和索引drop()则连集合带索引一起删掉没有后悔药。日常实验建议先countDocuments()确认影响行数再执行删除。4. 避坑与排查数据导完看不到、类型错乱、load 脚本失效的五个现场4.1 mongoimport 显示导入成功mongosh 里 find 返回空现象mongoimport终端打印imported N documents但切到 mongosh 执行db.scores.find()却什么都没返回。原因绝大多数情况下是库名不一致。mongoimport --db course把数据写进了course库而你在 mongosh 里执行的是use test或默认连接的是test库自然查不到。另一种可能是你同时装了多个 MongoDB 实例导入和查询连的不是同一个端口。解决先执行show dbs看实际存在哪些库确认数据落在哪个库如果库名对不上用use course或重新mongoimport指定正确的库。多实例场景下建议导入和查询都显式写端口或连接串避免依赖默认27017。4.2 mongoimport 提示 JSON 解析失败数组格式与每行对象格式混用现象执行mongoimport时报Failed: error processing document或者显示解析 JSON 时遇到invalid character/unexpected end of JSON input。原因scores_jsonArray.json整体是一个数组而mongoimport默认按 JSON Lines 格式处理也就是每行一个独立对象。数组文件在没加--jsonArray时工具从第一行[开始解析就失败了。解决数组格式文件加--jsonArray参数如果文件本身就是每行一个对象反而不要加。判断文件格式最快的方式是打开看第一行以[开头就是数组格式以{开头就是 JSON Lines 格式。也可以用 jq 先做一次合法性校验jq empty scores_jsonArray.json echo valid json如果 jq 报了语法错误多半是文件里有尾逗号、单引号或注释混入需要先修正 JSON 文件再导入。4.3 嵌套数组字段查不出来子文档精确匹配与点路径的差异现象db.scores.find({ scores: { score: { $gt: 90 } } })返回空结果或者结果明显不完整。原因把scores写成子文档精确匹配了。{ scores: { score: { $gt: 90 } } }要求数组元素要么是精确等于{ score: { $gt: 90 } }这个对象要么完全匹配这个结构。而实际数组元素里还有type字段所以匹配不上。解决数组内嵌字段的条件查询用点路径db.scores.find({ scores.score: { $gt: 90 } })。如果还需要同时约束type和score用$elemMatch确保条件落在同一个元素上。这个坑在实验数据量小的时候不容易暴露数据一多查不到结果就会让人怀疑数据没导对其实问题出在查询写法。4.4 聚合 $avg 算出 0 或 nullscore 字段类型不是数值现象$avg结果返回0、null或者平均值明显偏小偏大。原因$avg只会对数值字段求平均遇到字符串类型直接跳过。如果 JSON 文件里的score被写成80.1带引号或者导入前用 Excel 打开另存时把数值转成了文本导入后字段类型就是 string$avg无值可取。解决用$toDouble在聚合管道里显式转换类型db.scores.aggregate([ { $unwind: $scores }, { $group: { _id: $student_id, avg_score: { $avg: { $toDouble: $scores.score } } } } ]);$toDouble是 MongoDB 4.0 引入的类型转换操作符遇到非数值字符串会转成0或报错。更早版本可以用$convert。另外这个坑最好在导入前排查用findOne()看返回的score字段有没有引号包裹省得聚合阶段再处理。4.5 load_scores.js 执行失败或命令找不到工作目录与 PATH现象执行mongosh course load_scores.js报ENOENT: no such file or directory或在终端输入mongosh/mongoimport提示“不是内部或外部命令”。两种现象可能同时出现。原因前者是工作目录问题脚本里cat(scores_jsonArray.json)用相对路径你在其他目录执行脚本找不到同目录的数据文件。后者是安装问题mongosh 或 database tools 的 bin 目录没有加入系统 PATHWindows 下尤其常见。解决先cd到解压出来的“数据资源”目录再执行脚本相对路径就成立了。命令找不到的话去 MongoDB 安装目录找到mongosh.exe和mongoimport.exe所在路径加到系统环境变量 PATH 里如果复制到项目目录用也行但更推荐配置 PATH否则以后每次都要写全路径。装完 MongoDB Server 后 Windows 服务启动失败是另一类问题通常去查看 MongoDB 的 log 文件重点看data目录权限和路径配置这属于安装层面的问题和数据集本身无关。5. 用 explain 验证索引实验数据集的第一个性能调优动作数据导完之后我拿到任何新数据集都不会急着写功能代码而是先做一件事建索引再用explain看查询到底走没走索引。这不算玄学是 MongoDB 性能调优里最直接的验证手段scores 集合完全可以用来做这个实验。先建一个复合索引按学生和班级两个字段组织db.scores.createIndex({ student_id: 1, class_id: 1 });说明1表示升序索引。复合索引在查询同时带student_id和class_id条件时能显著加速。然后对带这两个条件的查询做执行计划分析db.scores.explain(executionStats).find({ student_id: 5, class_id: 1 });返回结果里重点看两部分winningPlan.stage和executionStats。winningPlan.stage是IXSCAN说明用上了索引是COLLSCAN说明还在全集合扫描executionStats.totalDocsExamined和executionStats.nReturned两个数字越接近说明扫描越精准。如果totalDocsExamined远大于nReturned索引就没生效或者选错了索引。复合索引有一个必须记住的边界最左前缀原则。上面建的{ student_id: 1, class_id: 1 }对只带class_id的查询通常不会生效因为复合索引要求查询条件从最左边的字段开始。想验证也很简单执行db.scores.explain(executionStats).find({ class_id: 1 })看winningPlan.stage大概率会看到COLLSCAN。这不是 MongoDB 的 bug是复合索引的设计约束。从那以后我每次拿到新的实验数据集导入完第一件事就是createIndex加explain确认查询路径再往下写功能。这套流程帮我避开过不少“明明建了索引查询还是很慢”的所谓玄学问题其实大多数是字段顺序不对或类型不匹配。希望帮到你。本文还有配套的精品资源点击获取
延伸阅读

更多相关文章

2026/10/4 7:46:23

用Python和PyMuPDF自建本地PDF工具箱:合并、拆分与图片转PDF实战

大家电脑里肯定都囤着一堆PDF文件:下载的电子书、扫描的合同、从网页导出的资料、截图拼成的长图……真到要用的时候,合并、拆分、转格式的需求就冒出来了。我之前也都是临时找在线工具,但用多了之后发现两个问题:一是广告和页数限…

2026/10/4 7:46:23

从库存负数看Linux多线程数据竞争:互斥锁与原子操作实战

前阵子给一个基于 Linux 的仓库管理项目做并发改造,压测时遇到一个特别典型的毛病:库存明明只有 100 件,模拟 50 个线程同时下单,跑完居然变成负数。一开始我以为是下单逻辑写错了,后来用 ThreadSanitizer 一查&#x…

2026/10/4 8:41:25

写智能工程机械毕业论文,别只盯“排行榜”:选对 AI 搭档,从挖掘机液压故障诊断说起 [特殊字符]

如果你学的是智能工程机械运用技术,大概率绕得过期末,却绕不过毕业前那个“又像机械、又像控制、又像物联网”的综合任务。 比如一个很典型的毕业设计题目:《基于振动与压力信号的挖掘机液压系统故障诊断方案设计》你需要完成的不只是一篇论文…

2026/10/4 8:41:25

基于ATmega324P与MR25H40CDF的工业存储设计:从选型到联调

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

2026/10/4 8:41:25

VMware虚拟网卡消失?VMnet1/VMnet8丢失的完整修复指南

装好了VMware Workstation,正准备开Linux虚拟机大干一场,结果在Windows的“网络连接”里一翻:完了,VMnet1和VMnet8一个都不在。这个情况我在Win10、Win11上都遇到过,也见过不少同事栽在同一个坑里——虚拟机装是装上了…

2026/10/4 8:41:25

OpenShell:Windows资源管理器替代方案与WSL深度集成指南

1. OpenShell 不是 Shell,而是 Windows 上的“资源管理器替代品” 很多人第一次看到 OpenShell 这个名字,会下意识联想到 Linux 的 bash 、 zsh ,或者 macOS 的 fish ——毕竟“Shell”这个词太有迷惑性了。但事实恰恰相反&#xff…

2026/10/4 0:01:02

Jev+Agent接管浏览器:browser-use实战与jev-ultrafast性能优化

1. 从“Jev”说起:为什么我要把Agent接进浏览器“Jev”这个词最近在圈子里出现的频率越来越高,很多人第一次听到会以为是某个新模型的名字,其实它更像是一种思路——把Jev模型的能力当作底座,通过Agent的方式去接管浏览器&#xf…

2026/10/4 0:01:02

多智能体集群实战:DeepAgents编排、MCP与A2A协议及Skills体系

1. 从"单兵作战"到"集群协同":多智能体编排到底在解决什么问题如果你最近在折腾 Agent 相关的东西,大概率会有一种感觉:单个 Agent 能做的事情,其实很快就摸到天花板了。你给它一个提示词,挂几个工…

2026/10/4 1:01:05

无源低通滤波器设计实战:从RC到LC,手把手教你避开那些坑

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

2026/10/4 0:01:02

Jev+Agent接管浏览器:browser-use实战与jev-ultrafast性能优化

1. 从“Jev”说起:为什么我要把Agent接进浏览器“Jev”这个词最近在圈子里出现的频率越来越高,很多人第一次听到会以为是某个新模型的名字,其实它更像是一种思路——把Jev模型的能力当作底座,通过Agent的方式去接管浏览器&#xf…

2026/10/4 0:01:02

多智能体集群实战:DeepAgents编排、MCP与A2A协议及Skills体系

1. 从"单兵作战"到"集群协同":多智能体编排到底在解决什么问题如果你最近在折腾 Agent 相关的东西,大概率会有一种感觉:单个 Agent 能做的事情,其实很快就摸到天花板了。你给它一个提示词,挂几个工…

2026/10/4 1:01:05

无源低通滤波器设计实战:从RC到LC,手把手教你避开那些坑

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

还想了解更多?直接咨询顾问

免费诊断 + 免费方案 + 透明报价。

全国咨询热线400-8866-253
免费获取方案
☎咨询二维码 ☎ ↑