
简介MongoDB实验数据集是一份面向数据库初学者与开发者的练习用数据包围绕MongoDB文档型数据库的核心操作设计适合用于课程实验、自学实践或功能验证。压缩包共2个文件包含js脚本和json数据文件整体仅30KBjs文件可用于批量导入与初始化集合json文件则存储模拟业务场景的文档记录虽精简却覆盖典型数据形态。目前已有857人学习下载。借助这些数据读者可以走通mongoimport导入流程练习find查询、aggregate聚合管道、字段更新与删除操作还能尝试建立索引以观察查询性能的变化并初步接触复制集、分片等分布式特性的配置思路。对于希望快速上手MongoDB并积累实操经验的学习者这份轻量级数据集提供了清晰、低成本的练习入口。1. MongoDB 实验数据集为什么你的实验总是复现不出别人的结果做 MongoDB 实验时最容易被低估的就是实验数据集。很多人从网上下一个现成 JSON 就往里灌等跑查询时才发现数据量太小看不出索引差异字段类型乱得没法做聚合想删几条数据还牵连了别的集合。MongoDB 实验数据集不是 JSON 的堆砌而是围绕业务场景设计、字段类型统一、带明确查询含义的文档集合。它直接决定了你能不能一遍跑通数据库、集合、文档这三个核心概念也决定索引和聚合实验有没有观察价值。这套东西适合两类人一类是做课程实验的学生任务文档里写着「文档数据在 MongoDB 中的查询和删除」拿数据集把每步操作验证清楚另一类是刚接手 MongoDB 的开发者想用一套可控数据快速验证生产方案。数据集的坑不在生成那一下在设计和导入。2. 从业务场景倒推数据集设计先画文档结构再写生成脚本常见做法是打开 mongosh 手动敲 insertOne塞两条数据就开始实验。语法验证够了但一涉及执行计划和聚合几百条数据什么都看不出来。我一般会先想清楚这次实验要回答什么问题再倒推数据集结构。实验要复现的是查询逻辑、索引效果、聚合结果那数据集就得能同时支撑这三件事而不是随便造一个扁平数组。2.1 数据库、集合、文档三层结构先落在纸面上MongoDB 的核心概念就三层数据库database、集合collection、文档document。实验数据集必须让这三层各司其职否则后面写查询全靠猜。我的分法很简单一个实验用一个库比如 exp_mongodb避免课程不同关卡的实验数据互相污染一个业务实体一个集合用户放 users行为日志放 user_logs一条记录一个文档字段类型稳定统一这是查询不翻车的前提。拿用户行为日志举例users 和 user_logs 是两个集合users 里的 user_id 是主键属性user_logs 里通过 user_id 关联到用户。这个一对多的关系不是摆设后面做 $lookup 关联实验时它就是练习 join 的现成素材。集合之间不要设计成「一个集合装所有东西」那是关系型数据库时代遗留的坏习惯在 MongoDB 里会带来两个问题文档字段膨胀导致单文档体积接近 16MB 上限以及无法针对单一业务维度做索引。user_logs 里的一条文档长这样{ log_id: 100001, user_id: 4362, action: purchase, page: /pay, device: ios, duration_ms: 15230, timestamp: 2025-06-05T14:23:11 }字段是刻意挑的log_id 练等值查询user_id 练关联和类型陷阱duration_ms 练范围查询page 练正则匹配action 练分组聚合timestamp 练时间窗口。每个字段对应一个具体实验任务而不是堆一堆用不上的属性。如果实验还要练全文检索可以再加一个 description 字段放商品描述如果要练嵌套文档就在订单集合里嵌一个 items 数组。设计文档结构时先列实验任务清单再反推字段这一条比任何生成技巧都重要。2.2 用 Python 生成 10 万条行为日志脚本与三个关键参数手工构造数据到几千条就撑不住了。我用一个生成脚本批量产出用户和行为日志数据量、字段比例、污染比例都可控import random import datetime import json random.seed(42) # 固定随机种子保证每次生成的数据一致 actions [view, click, search, add_cart, purchase] pages [/home, /list, /detail, /cart, /pay] devices [ios, android, pc] # 1. 生成 5000 个用户文档 users [] for uid in range(1, 5001): users.append({ user_id: uid, name: fuser_{uid}, level: random.randint(1, 5), register_date: ( datetime.datetime(2024, 1, 1) datetime.timedelta(daysrandom.randint(0, 300)) ).isoformat(), }) # 2. 生成 10 万条行为日志 logs [] for i in range(100000): ts datetime.datetime(2025, 6, 1) datetime.timedelta( secondsrandom.randint(0, 7 * 24 * 3600) ) logs.append({ log_id: i 1, user_id: random.randint(1, 5000), action: random.choice(actions), page: random.choice(pages), device: random.choice(devices), duration_ms: random.randint(200, 20000), timestamp: ts.isoformat(), }) # 故意埋两类脏数据空字符串 action、字符串类型 user_id if i % 97 0: logs[-1][action] if i % 131 0: logs[-1][user_id] str(logs[-1][user_id]) # 3. 写成 JSON Lines每行一个文档 with open(users.json, w) as f: for u in users: f.write(json.dumps(u, ensure_asciiFalse) \n) with open(user_logs.json, w) as f: for l in logs: f.write(json.dumps(l, ensure_asciiFalse) \n) print(users:, len(users)) print(user_logs:, len(logs))这里三个选择值得说清楚。第一输出用 JSON Lines 而不是 JSON 数组每行一个文档mongoimport 可以直接流式读入不用先把整个数组加载进内存10 万条也是秒级导入。第二时间字段用 isoformat() 写成 ISO 8601 字符串这种格式的字典序和时间的先后序一致后面用字符串比较也能做范围查询实验门槛低。第三random.seed(42) 固定随机种子同一份脚本在任何机器上生成的数字序列完全一致别人拿你的数据集做对比才有可比性。数据量为什么是 10 万而不是 100 万百万级导入和索引构建在笔记本上会拖到分钟级课程实验的耐心撑不住几千条数据建不建索引根本没差别COLLSCAN 和 IXSCAN 都是毫秒级返回。10 万是我反复试下来最能同时体现全表扫描和索引加速差异的体量也符合大多数教学环境的内存和磁盘限制。2.3 故意埋两类脏数据查询和删除实验需要「杂质」完整干净的数据集只适合练语法不适合练处理逻辑。删除实验要有删除目标类型陷阱要有触发条件。我故意埋了两类杂质一部分记录的 action 是空字符串一部分 user_id 被写成了字符串类型。这两类问题在真实业务里极其常见——埋点上报丢失、上游接口类型转换失误拿它们做实验素材练出来的处理能力能直接迁移到生产。间隔用质数 97 和 131是刻意选的。它们和 100000 的所有因子互质不会和样本总数的约数产生周期重合杂质分布看起来均匀又不规则你不会一眼看出脏数据的位置。实验里先确认杂质落位数量mongosh --quiet exp_mongodb --eval db.user_logs.countDocuments({ action: })这个数量在 1000 条左右看到非零且数量合理说明导入的数据是完整的。同时也可以用 $type 查字符串类型的 user_idmongosh --quiet exp_mongodb --eval db.user_logs.countDocuments({ user_id: { $type: string } })杂质是数据集设计的一部分和业务字段一样需要写进说明文档。拿到你数据集的人必须知道这里有多少脏数据、怎么识别它们否则后面的删除实验和类型转换实验会被当成数据质量问题。3. mongoimport 灌数据JSON 与 CSV 的格式选择以及三个必调参数数据集生成出来是文件要进 MongoDB 得先让 mongod 跑起来再用 mongoimport 导入。格式选错、参数漏掉后面查询阶段全部白搭。3.1 导入前先确认 mongod 活着ping 命令与连接判断导入前先确认 mongod 是活着的。最稳的做法是用 mongosh 发一个 pingmongosh --quiet --eval db.runCommand({ ping: 1 })返回 ok: 1 说明连接正常。连接不上时先看 mongod 日志别急着重装——这个排查思路我放到第 5 章展开。如果是课程平台直接提供的远程环境mongod 通常已经帮你启动好了只需要拿着连接字符串往下走。特别提醒如果远程环境开的是 27017 之外的自定义端口连接串里要带端口号很多人在这一步卡住是因为默认端口对不上。3.2 JSON Lines 导入--drop 和 --numInsertionWorkers 的取舍导入 2.2 节生成的 user_logs.jsonmongoimport --db exp_mongodb --collection user_logs \ --file user_logs.json \ --drop \ --numInsertionWorkers 4--db 指定库--collection 指定集合。MongoDB 的库和集合不用预先建mongoimport 在写入第一条文档时自动创建前提是连接用户对该库有写权限。--drop 是重复导入的后悔药导入前先删掉同名集合避免上一次实验残留的数据和新数据叠加成 20 万条。--numInsertionWorkers 4 开 4 个并发插入线程10 万条数据默认单线程两三秒4 线程明显更快本机实验开到 4 就够再往上边际收益很低反而让 CPU 飙高。提示重复导入同一个文件时务必带 --drop。没带的话集合里数据直接翻倍后面 count 结果和实验预期对不上还以为是导入丢数据。这三个参数里最容易翻车的是 --drop。实验做第二遍、第三遍时集合里已经有上一轮的旧数据不带 --drop 跑出来的 total 是 20 万、30 万排查半天才发现是重复导入。数据文件优先用 JSON因为字段类型整数、字符串、布尔能被 mongoimport 原样保留CSV 会丢类型除非你按 3.3 节的办法显式声明。3.3 CSV 导入会丢类型--columnsHaveTypes 的完整写法某些课程任务规定必须用 CSV 作为交换格式这时注意 mongoimport 默认把 CSV 里每个字段都当字符串读进来。duration_ms 变成字符串后$gt 范围查询和 $avg 聚合全部乱套integer 类操作符也会隐式转换失败。正确做法是在生成脚本里同步输出 users.csv然后带类型映射导入import csv with open(users.csv, w, newline) as f: writer csv.DictWriter(f, fieldnames[user_id, name, level, register_date]) writer.writeheader() writer.writerows(users)mongoimport --db exp_mongodb --collection users \ --type csv --file users.csv --headerline \ --columnsHaveTypes \ --fields user_id.int32(),name.string(),level.int32(),register_date.date_go(2006-01-02T15:04:05.000Z)--headerline 让第一行作为字段名--columnsHaveTypes 配合 --fields 给每列声明类型。date_go 里的模板是 mongoimport 特有的 Go 时间布局格式写错一位就报解析错误。如果 CSV 里时间字段格式统一且你不想折腾模板可以先把时间列当 string 导进去入库后再用 updateMany 转 Date。优先走 JSONCSV 是任务限制下的妥协方案能少碰就少碰。3.4 导入完先验一遍count、stats、findOne 三连导入成功不等于导入正确。我每轮导入后固定跑三条命令mongosh --quiet exp_mongodb --eval db.user_logs.countDocuments({}) mongosh --quiet exp_mongodb --eval db.user_logs.stats(1024 * 1024) mongosh --quiet exp_mongodb --eval db.user_logs.findOne()countDocuments 核对总数是不是 100000stats 看集合占用空间size 和 storageSize 的差别能反映存储引擎的压缩策略和索引数量findOne 抽查一条文档确认字段类型没在导入时被改掉。这里有个省事技巧stats 输出里的 indexSizes 字段能直接看到索引用了多少内存如果索引占了集合空间的 30% 以上说明索引建多了实验数据集的集合不需要那么多索引。图形界面党可以用 NoSQLBooster for MongoDB 这类客户端连上来扫一眼集合字段类型分布导入异常一眼就能看出来不用在命令行里反复切来切去。4. 文档数据在 MongoDB 中的查询和删除把操作符在数据集上跑一遍课程实验往往把这一关任务写成「文档数据在 MongoDB 中的查询和删除」数据集的价值就在这一章兑现。查询不是背语法而是让每一步操作都能在一个明确的集合上看到影响范围——查了多少条、删了多少条、剩余多少条全都有数。4.1 find 查询等值、范围、正则三种写法与 explain 验证拿 user_logs 练三种最常用的条件写法// 等值查询精确匹配某个用户 db.user_logs.find({ user_id: 4362 }) // 范围查询$gt/$lte 组合找耗时长于一秒的操作 db.user_logs.find({ duration_ms: { $gt: 10000, $lte: 20000 } }) // 正则查询匹配以 /detail 开头的页面 db.user_logs.find({ page: /^\/detail/ })字段设计在这一步体现出价值。等值查询查 user_id 和 action范围查询查 duration_ms 和 timestamp正则查询查 page。先别急着建索引直接看全表扫描的执行计划db.user_logs.find({ action: purchase }).explain(executionStats)explain 输出里两个字段最关键totalDocsExamined 和 totalKeysExamined。全表扫描时 totalDocsExamined 约等于集合总量 10 万totalKeysExamined 为 0。这时给 action 建索引db.user_logs.createIndex({ action: 1 })再跑一次 explaintotalKeysExamined 会变成索引命中的数量totalDocsExamined 降到接近命中量。10 万条数据的体量在这里恰好能把 COLLSCAN 和 IXSCAN 的差别放大到肉眼可见。还可以试试复合索引 { action: 1, duration_ms: 1 }把按 action 过滤再按 duration_ms 排序的查询从内存排序变成索引排序explain 的 SORT 阶段会直接消失。4.2 删除实验deleteOne、deleteMany 与 drop 的边界删除是实验里最怕误操作的一步MongoDB 的普通删除操作没有事务回滚删了就是删了。对应课程任务先统计再删除是铁律// 先看影响范围 db.user_logs.countDocuments({ action: }) // 清理空 action 的脏数据 db.user_logs.deleteMany({ action: }) // 删除单条 db.user_logs.deleteOne({ log_id: 1 }) // 再确认删除效果 db.user_logs.countDocuments({ action: })deleteMany 删除所有匹配文档deleteOne 只删第一条匹配文档。顺序不能反先 count 确认影响行数再执行删除删完再 count 一次确认归零。如果只想清掉某个字段而不是删整条文档用 updateMany 配合 $unset db.user_logs.updateMany({}, { $unset: { temp_field: } })两者语义完全不同——delete 是删文档$unset 是删字段。整集合重来则用 drop() 删集合和索引速度比逐条 deleteMany 快几个数量级。课程实验里最常见的尴尬局面是上一轮把脏数据删干净了下一轮又要拿脏数据做删除实验。这时直接 drop 集合再重跑一遍导入比重新生成数据省事得多。注意deleteMany 之前先 count 确认影响范围没有后悔药。drop 集合之前更要确认集合名没打错drop 错集合就只能重新导入整个数据集。4.3 聚合管道$match、$group、$sort 做一次流量分析查询和删除都验证完后聚合管道是数据集观察价值的放大器。下面这段把一周内的日志按 action 分组统计次数和平均耗时db.user_logs.aggregate([ // 先过滤只看前七天 { $match: { timestamp: { $gte: 2025-06-01T00:00:00, $lt: 2025-06-08T00:00:00 } } }, // 再分组按 action 统计次数和平均耗时 { $group: { _id: $action, count: { $sum: 1 }, avg_duration: { $avg: $duration_ms } } }, // 最后排序次数降序 { $sort: { count: -1 } } ])$match 把过滤推到最前面减少后续 stage 处理的数据量这是聚合管道调优的第一原则$group 按 action 分组$sum 和 $avg 是实验里最常用的累加器$sort 最后做全局排序。timestamp 是 ISO 字符串字符串字典序在这里恰好等于时间序所以 $gte/$lt 直接套用字符串即可。想验证关联查询可以给 users 和 user_logs 补一个 $lookup 阶段把 user_id 关联回用户维度再按用户等级分组。整个实验链路从单集合查询走到多集合关联课程任务要求的核心操作基本全部覆盖。5. MongoDB 实验数据集常见问题排查安装失败、导入乱码与索引失效数据集本身没问题跑不起来多半在环境和导入环节。下面几个坑我每轮实验几乎都会遇到按排查优先级列出来。5.1 场景一mongod 启动失败dbpath 与权限问题现象mongod 启动后立刻退出日志里提示 /data/db 不存在或 permission denied。原因Linux 下 mongod 默认数据目录是 /data/db普通用户既没创建这个目录也没有写入权限。这是 MongoDB 安装失败里最常见的单一原因。解决先建目录并授权再启动sudo mkdir -p /data/db sudo chown -R $(whoami) /data/db mongod --dbpath /data/db --fork --logpath /tmp/mongod.log--fork 让 mongod 后台运行--logpath 指定日志位置。如果还起不来直接看 /tmp/mongod.log 最后 20 行端口被占用、磁盘空间不足、配置文件语法错误都会明确写出来。绝大多数启动失败都能从日志里找到答案不用反复重装。5.2 场景二系统源装的 MongoDB 版本太旧现象mongod 能启动但 mongoimport 导入报版本不匹配或者课程脚本里某些聚合操作符不存在。原因操作系统自带的软件源里 MongoDB 版本通常落后好几个大版本比如某些 Linux 发行版源里还是 3.x行为特性和课程环境不一致。版本不一致是实验行为走样的最大来源。解决用 MongoDB 官方源安装或者干脆用 Docker 起一个干净的实例docker run -d --name mongodb-exp -p 27017:27017 -v $PWD/data:/data/db mongo用 Docker 的额外好处是环境隔离干净实验做完 docker rm 直接重置不用在宿主机上做任何清理。安装 MongoDB 之前先确认 mongod --version 的输出和课程文档要求的版本匹配版本相关的怪问题八成是版本不一致引起的先统一环境再排查别一头扎进代码里。5.3 场景三导入乱码与 BSON 16MB 上限现象mongoimport 导入后中文显示成问号或者导入报错提示 object is larger than 16MB。原因乱码是文件编码问题。Windows 下编辑器默认可能是 GBKmongoimport 按 UTF-8 读自然乱码。16MB 报错是单条 BSON 文档体积超过硬上限JSON 里嵌一个大数组就会触发。解决生成脚本写文件时显式指定 UTF-8比如 Python 的 open 加 encodingutf-8或者用 VS Code 把文件转成 UTF-8 无 BOM 再导入。16MB 是 BSON 格式的硬约束解决思路是拆文档大字段拆到子集合或改成引用式设计不要塞进一条文档里。数据集设计阶段就要避开超大数组一条文档嵌几千个元素的情况直接判设计不合格这也是为什么 2.1 节强调按业务实体拆集合。5.4 场景四查询慢与索引失效类型不一致是元凶现象建了索引explain 里还是 COLLSCAN或者走了 IXSCAN但 totalKeysExamined 比索引命中的数量多得多。原因最多见的是字段类型不一致。2.2 节故意把一部分 user_id 写成字符串混合类型会让索引选择器失效或部分命中。这类问题不报错只会让查询慢到崩溃还查不对结果。解决先用类型过滤确认脏数据规模db.user_logs.countDocuments({ user_id: { $type: string } })确认后用聚合管道更新把字符串转回整数db.user_logs.updateMany( { user_id: { $type: string } }, [{ $set: { user_id: { $toInt: $user_id } } }] )这是 MongoDB 4.2 起支持的管道更新语法$toInt 在聚合表达式里做类型转换。跑完再查一次类型分布确认没有残留字符串然后重建索引explain 里的扫描数通常掉一个数量级。类型陷阱优先级最高因为它是「不报错的错误」只能靠 explain 和 $type 排查。5.5 场景五开启鉴权的环境里导入连不上现象mongoimport 报 Authentication failed本地 mongosh 却能连。原因课程平台比如头歌的实验环境默认开启鉴权和用户体系导入命令里没带认证参数走的是匿名连接。解决连接串里带用户名密码和认证库mongoimport --uri mongodb://exp_user:exp_password127.0.0.1:27017/exp_mongodb?authSourceadmin \ --collection user_logs --file user_logs.json --dropauthSourceadmin 指定认证库实验环境的用户通常在 admin 库下创建。带了鉴权后--db 参数或 URI 里的库名必须和用户的授权库一致否则权限校验过不去。遇到鉴权类报错先在 mongosh 里用同样的用户名密码试一次登录能通则说明授权没问题问题出在连接参数上逐项比对 URI 里的用户名、密码、authSource 三个部分。6. 让实验数据集可复用校验脚本与版本化两个习惯数据集不是一次性消耗品。课程作业、团队内部共享、隔几周再回来做对比实验可复现比数据本身更值钱。6.1 用一个校验脚本导入后一键确认数据完整性把总数、脏数据量、类型错误量一次打出来// verify_dataset.js const db db.getSiblingDB(exp_mongodb); const total db.user_logs.countDocuments({}); const dirty db.user_logs.countDocuments({ action: }); const typeError db.user_logs.countDocuments({ user_id: { $type: string } }); print(total${total}, dirty${dirty}, typeError${typeError}); if (total ! 100000) quit(1); if (typeError 200) quit(2);在 bash 里直接跑mongosh --quiet verify_dataset.jsdb.getSiblingDB(exp_mongodb) 不改变 mongosh 当前的连接目标脚本可移植到任何库。quit(code) 让退出码可控课程平台的自动判题、团队里的 CI 流程都能直接拿这个退出码做判断。6.2 生成脚本、校验脚本、数据文件一起进版本库第二个习惯生成脚本、校验脚本、数据文件放同一个目录一起进 Git。别人拿到仓库跑一遍 generate 脚本就能得到和你完全一致的数据集——random.seed 固定了随机源版本差异只来源于代码差异不来源于随机数。改字段先改生成脚本不要手动改 JSON。手动改一条容易改一万条就是灾难而且改完校验脚本会因为脏数据比例变化报警。我自己的习惯是每轮实验前先跑一遍校验脚本确认上一轮实验有没有把数据搞坏。删除实验会把脏数据清掉下一轮就没有删除目标了类型转换实验会把字符串 user_id 修掉下一轮类型陷阱实验就复现不出来。这时 drop 集合重新导入一次一分钟把环境拉回初始状态。这个习惯救过我很多次尤其是隔了两周再回来做对比实验的时候。实验数据集的价值一半在生成另一半在可复现希望这些做法能帮到你。本文还有配套的精品资源点击获取