一、前言在掌握了MongoDB的基础概念和集群部署之后真正的功力体现在日常CRUD操作的熟练度上。本文将通过三个递进式实战练习系统讲解MongoDB的条件查询、数组操作、内嵌文档处理以及文档关系建模。所有代码均在MongoDB Shell中实测通过建议边读边敲。二、练习一CRUD基础与数组操作2.1 环境准备与基本插入首先进入test数据库观察初始状态// 进入test数据库use test;// 查看当前数据库中所有集合show collections;// 查询students集合中的文档假设已有数据db.students.find();向users集合插入第一条文档db.users.insertOne({username:lyh});再次查看集合列表会发现users集合被自动创建show collections;继续插入第二条文档并验证db.users.insertOne({username:sxau});// 查看所有文档db.users.find();// 统计文档数量db.users.find().count();2.2 条件查询入门// 查询username为lyh的文档db.users.find({username:lyh});2.3 更新操作详解$set添加/修改字段为lyh添加address属性db.users.updateOne({username:lyh},{$set:{address:shanxi}});replaceOne整体替换文档将sxau的文档整体替换为{username: lj}db.users.replaceOne({username:sxau},{username:lj});⚠️注意replaceOne会完全替换整个文档原文档的其他字段将全部丢失$unset删除字段删除lyh的address属性db.users.updateOne({username:lyh},{$unset:{address:404}}// 值可以是任意值unset只看键名);2.4 内嵌文档操作MongoDB的文档属性值可以是另一个文档称为内嵌文档。这是MongoDB区别于关系型数据库的核心特性之一。为lyh添加hobby内嵌文档db.users.updateOne({username:lyh},{$set:{hobby:{cities:[chengdu,xian],sports:[basketball,football]}}});为lj也添加hobbydb.users.updateOne({username:lj},{$set:{hobby:{sport:[basketball,sing]}}});2.5 内嵌文档查询查询喜欢basketball的用户db.users.find({hobby.sport:basketball});关键点查询内嵌文档属性时属性名必须使用点号表示法hobby.sport且必须加引号2.6 数组操作$push vs $addToSet$push向数组添加元素允许重复db.users.updateOne({username:lj},{$push:{hobby.sport:pingpang}});再次执行同样的$push会发现数组中出现了两个pingpang。$addToSet向数组添加元素去重db.users.updateOne({username:lj},{$addToSet:{hobby.sport:pingpang}});再次执行pingpang不会重复添加因为$addToSet将数组视为Set集合。操作符重复元素适用场景$push允许日志记录、时间线等允许重复的场景$addToSet不允许标签、分类等需要去重的场景2.7 删除操作删除喜欢sing的用户db.users.deleteOne({hobby.sport:sing});三、练习二条件查询与分页实战3.1 批量插入的两种方案对比方案一循环单条插入慢for(vari1;i20000;i){db.numbers.insertOne({num:i});}// 耗时约十几秒方案二数组批量插入快db.numbers.drop();// 先清空vararr[];for(vari1;i20000;i){arr.push({num:i});}db.numbers.insertMany(arr);// 耗时约1秒提升10倍以上性能结论insertMany将网络往返次数从20000次降到1次是大数据量导入的唯一正确姿势。3.2 比较运算符查询// 等于查询db.numbers.find({num:500});// 大于查询 (greater than)db.numbers.find({num:{$gt:18888}});// 小于等于查询 (less than or equal)db.numbers.find({num:{$lte:30}});// 等于查询 (equal显式写法)db.numbers.find({num:{$eq:77}});// 范围查询大于50且小于70db.numbers.find({num:{$gt:50,$lt:70}});运算符含义示例$eq等于{num: {$eq: 77}}$gt大于{num: {$gt: 18888}}$lt小于{num: {$lt: 30}}$gte大于等于{num: {$gte: 100}}$lte小于等于{num: {$lte: 30}}$ne不等于{num: {$ne: 500}}3.3 分页查询// 查询前10条第1页db.numbers.find().limit(10);// 查询第10-20条第2页db.numbers.find().skip(10).limit(10);// 查询第21-30条第3页db.numbers.find().skip(20).limit(10);MongoDB会自动调整顺序db.numbers.find().limit(10).skip(20)实际执行时等价于skip(20).limit(10)。⚠️生产建议skip在大数据量时性能较差需要扫描跳过的文档。生产环境建议使用范围分页{num: {$gt: lastNum}}.limit(10)。四、练习三文档关系建模实战MongoDB没有外键约束文档关系通过内嵌文档或**引用ObjectId数组**来实现。下面通过三种经典关系场景逐一讲解。4.1 一对一关系夫妻模型场景一个妻子对应一个丈夫。建模方案将丈夫信息作为内嵌文档嵌入妻子文档。db.wifeAndHusband.insertMany([{name:狄如燕,husband:{name:李元芳}},{name:红太狼,husband:{name:灰太狼}}]);查询结果结构_id: ObjectId(64e1730b7867901a96e5f810) name: 狄如燕 husband: {name: 李元芳} _id: ObjectId(64e1730b7867901a96e5f811) name: 红太狼 husband: {name: 灰太狼}✅适用场景关联数据量小、查询总是一起访问、不需要独立更新。4.2 一对多关系文章与评论场景一篇文章对应多条评论。建模方案将评论数组直接嵌入文章文档。db.article.insertOne({name:《沉默的大多数》,comments:[很好看,给王小波点赞,神作]});// 为所有文章添加作者字段db.article.updateMany({name:{$exists:true}},{$set:{author:王小波}});// 插入第二篇文章db.article.insertOne({name:《祈念守护人》,author:东野圭吾,comments:[很治愈,好看,力推]});查询结果_id: ObjectId(...f825) name: 《沉默的大多数》 author: 王小波 comments: [很好看, 给王小波点赞, 神作] _id: ObjectId(...f827) name: 《祈念守护人》 author: 东野圭吾 comments: [很治愈, 好看, 力推]⚠️注意事项如果评论数量可能无限增长如微博评论不建议全部内嵌应考虑分页内嵌或单独集合引用。4.3 多对多关系老师与学生场景一个学生可以跟多个老师学习一个老师可以教多个学生。建模方案学生文档中通过teacher_ids数组存储关联老师的ObjectId。步骤1创建老师集合db.teachers.insertMany([{name:狄仁杰},{name:曾泰},{name:李元芳}]);步骤2创建学生集合db.students.insertMany([{name:狄春,age:22,isDel:0,gender:男},{name:狄如燕,age:20,isDel:0,gender:女}]);步骤3建立关联关系假设teachers集合中狄仁杰的_id为64e17b707867901a96e5f81e李元芳的_id为64e17b707867901a96e5f820db.students.updateOne({name:狄春},{$set:{teacher_ids:[ObjectId(64e17b707867901a96e5f81e),// 狄仁杰ObjectId(64e17b707867901a96e5f820)// 李元芳]}});查询结果_id: ObjectId(...f822) name: 狄春 age: 22 gender: 男 isDel: 0 teacher_ids: [ObjectId(64e17b70...81e), ObjectId(64e17b70...820)]关联查询MongoDB 3.2 支持$lookup聚合实现类似SQL的JOIN操作但性能不如内嵌文档建议根据查询模式选择建模方案。五、练习四条件查询进阶与$inc操作5.1 准备测试数据创建部门表和员工表db.dept.insertMany([{deptno:1001,dname:财务部,loc:北京},{deptno:1002,dname:办公室,loc:上海},{deptno:1003,dname:销售部,loc:成都},{deptno:1004,dname:运营部,loc:西安}]);db.emp.insertMany([{empno:7001,ename:光头强,job:伐木工,depno:1004,sal:800},{empno:7002,ename:熊大,job:护林员,depno:1004,sal:100},{empno:7003,ename:熊二,job:护林员,depno:1004,sal:500},{empno:7004,ename:吉吉,job:老板,depno:1001,sal:7000},{empno:7005,ename:毛毛,job:会计,depno:1002,sal:2000},{empno:7006,ename:二狗,job:保安,depno:1003,sal:1000},{empno:7007,ename:大马猴,job:保安,depno:1003,sal:1000}]);5.2 条件查询实战查询工资小于1100的员工db.emp.find({sal:{$lt:1100}});结果光头强(800)、熊大(100)、熊二(500)、二狗(1000)、大马猴(1000)查询工资在500-1000之间的员工db.emp.find({sal:{$gt:500,$lt:1000}});结果光头强(800)注意$gt: 500, $lt: 1000是开区间不包含边界值。若要包含边界请使用$gte和$lte。查询工资小于1000或大于2000的员工db.emp.find({$or:[{sal:{$lt:1000}},{sal:{$gt:2000}}]});结果光头强(800)、熊大(100)、熊二(500)、吉吉(7000)查询运营部所有员工// 先查运营部的deptnovardepnodb.dept.findOne({dname:运营部}).deptno;// 再用deptno查员工db.emp.find({depno:depno});结果光头强、熊大、熊二depno均为10045.3 $inc原子自增给所有工资低于等于1000的员工涨薪500元db.emp.updateMany({sal:{$lte:1000}},{$inc:{sal:500}});执行后光头强800 → 1300熊大100 → 600熊二500 → 1000二狗1000 → 1500大马猴1000 → 1500$inc 特性原子操作并发安全无需先读后写支持负数{$inc: {sal: -200}}实现减薪只能用于数值字段六、排序与投影精准控制输出6.1 sort排序// 默认按_id排序db.emp.find();// 按工资升序排序db.emp.find().sort({sal:1});// 按工资升序工资相同则按empno降序db.emp.find().sort({sal:1,empno:-1});值含义1升序 (Ascending)-1降序 (Descending)链式调用limit、skip、sort可以以任意顺序调用MongoDB会自动优化执行顺序。6.2 投影控制返回字段find()的第二个参数用于投影指定返回哪些字段// 只返回ename字段_id默认返回db.emp.find({},{ename:1});// 只返回ename不返回_iddb.emp.find({},{ename:1,_id:0});// 排除sal字段返回其他所有字段db.emp.find({},{sal:0});值含义1显示该字段0隐藏该字段⚠️规则投影中不能同时混用1和0_id除外。即要么指定要显示的字段其余隐藏要么指定要隐藏的字段其余显示。七、知识点速查表类别操作语法插入单条db.col.insertOne(doc)批量db.col.insertMany([doc1, doc2])查询等于{field: value}大于{field: {$gt: value}}小于等于{field: {$lte: value}}范围{field: {$gt: a, $lt: b}}OR条件{$or: [{cond1}, {cond2}]}更新修改字段{$set: {field: value}}删除字段{$unset: {field: 1}}原子自增{$inc: {field: n}}数组追加{$push: {field: value}}数组去重追加{$addToSet: {field: value}}整体替换db.col.replaceOne(filter, newDoc)删除单条db.col.deleteOne(filter)批量db.col.deleteMany(filter)分页限制数量.limit(n)跳过数量.skip(n)排序升序.sort({field: 1})降序.sort({field: -1})投影显示字段.find({}, {field: 1})隐藏字段.find({}, {field: 0})八、总结本文通过四个递进式练习系统覆盖了MongoDB CRUD的核心技能基础CRUDinsertOne/insertMany、find、updateOne/updateMany、deleteOne的基本用法数组与内嵌文档$pushvs$addToSet、点号查询hobby.sport、内嵌文档建模条件查询与分页比较运算符、$or、范围查询、limit/skip分页文档关系建模一对一内嵌、一对多数组内嵌、多对多ObjectId引用进阶操作$inc原子自增、sort排序、投影控制字段核心心法MongoDB的灵活性是双刃剑——模式自由让你快速迭代但也需要你在应用层保证数据一致性。选择合适的文档关系建模方案是MongoDB设计的关键。
MongoDB CRUD实战练习题精讲
发布时间:2026/6/8 6:26:27
一、前言在掌握了MongoDB的基础概念和集群部署之后真正的功力体现在日常CRUD操作的熟练度上。本文将通过三个递进式实战练习系统讲解MongoDB的条件查询、数组操作、内嵌文档处理以及文档关系建模。所有代码均在MongoDB Shell中实测通过建议边读边敲。二、练习一CRUD基础与数组操作2.1 环境准备与基本插入首先进入test数据库观察初始状态// 进入test数据库use test;// 查看当前数据库中所有集合show collections;// 查询students集合中的文档假设已有数据db.students.find();向users集合插入第一条文档db.users.insertOne({username:lyh});再次查看集合列表会发现users集合被自动创建show collections;继续插入第二条文档并验证db.users.insertOne({username:sxau});// 查看所有文档db.users.find();// 统计文档数量db.users.find().count();2.2 条件查询入门// 查询username为lyh的文档db.users.find({username:lyh});2.3 更新操作详解$set添加/修改字段为lyh添加address属性db.users.updateOne({username:lyh},{$set:{address:shanxi}});replaceOne整体替换文档将sxau的文档整体替换为{username: lj}db.users.replaceOne({username:sxau},{username:lj});⚠️注意replaceOne会完全替换整个文档原文档的其他字段将全部丢失$unset删除字段删除lyh的address属性db.users.updateOne({username:lyh},{$unset:{address:404}}// 值可以是任意值unset只看键名);2.4 内嵌文档操作MongoDB的文档属性值可以是另一个文档称为内嵌文档。这是MongoDB区别于关系型数据库的核心特性之一。为lyh添加hobby内嵌文档db.users.updateOne({username:lyh},{$set:{hobby:{cities:[chengdu,xian],sports:[basketball,football]}}});为lj也添加hobbydb.users.updateOne({username:lj},{$set:{hobby:{sport:[basketball,sing]}}});2.5 内嵌文档查询查询喜欢basketball的用户db.users.find({hobby.sport:basketball});关键点查询内嵌文档属性时属性名必须使用点号表示法hobby.sport且必须加引号2.6 数组操作$push vs $addToSet$push向数组添加元素允许重复db.users.updateOne({username:lj},{$push:{hobby.sport:pingpang}});再次执行同样的$push会发现数组中出现了两个pingpang。$addToSet向数组添加元素去重db.users.updateOne({username:lj},{$addToSet:{hobby.sport:pingpang}});再次执行pingpang不会重复添加因为$addToSet将数组视为Set集合。操作符重复元素适用场景$push允许日志记录、时间线等允许重复的场景$addToSet不允许标签、分类等需要去重的场景2.7 删除操作删除喜欢sing的用户db.users.deleteOne({hobby.sport:sing});三、练习二条件查询与分页实战3.1 批量插入的两种方案对比方案一循环单条插入慢for(vari1;i20000;i){db.numbers.insertOne({num:i});}// 耗时约十几秒方案二数组批量插入快db.numbers.drop();// 先清空vararr[];for(vari1;i20000;i){arr.push({num:i});}db.numbers.insertMany(arr);// 耗时约1秒提升10倍以上性能结论insertMany将网络往返次数从20000次降到1次是大数据量导入的唯一正确姿势。3.2 比较运算符查询// 等于查询db.numbers.find({num:500});// 大于查询 (greater than)db.numbers.find({num:{$gt:18888}});// 小于等于查询 (less than or equal)db.numbers.find({num:{$lte:30}});// 等于查询 (equal显式写法)db.numbers.find({num:{$eq:77}});// 范围查询大于50且小于70db.numbers.find({num:{$gt:50,$lt:70}});运算符含义示例$eq等于{num: {$eq: 77}}$gt大于{num: {$gt: 18888}}$lt小于{num: {$lt: 30}}$gte大于等于{num: {$gte: 100}}$lte小于等于{num: {$lte: 30}}$ne不等于{num: {$ne: 500}}3.3 分页查询// 查询前10条第1页db.numbers.find().limit(10);// 查询第10-20条第2页db.numbers.find().skip(10).limit(10);// 查询第21-30条第3页db.numbers.find().skip(20).limit(10);MongoDB会自动调整顺序db.numbers.find().limit(10).skip(20)实际执行时等价于skip(20).limit(10)。⚠️生产建议skip在大数据量时性能较差需要扫描跳过的文档。生产环境建议使用范围分页{num: {$gt: lastNum}}.limit(10)。四、练习三文档关系建模实战MongoDB没有外键约束文档关系通过内嵌文档或**引用ObjectId数组**来实现。下面通过三种经典关系场景逐一讲解。4.1 一对一关系夫妻模型场景一个妻子对应一个丈夫。建模方案将丈夫信息作为内嵌文档嵌入妻子文档。db.wifeAndHusband.insertMany([{name:狄如燕,husband:{name:李元芳}},{name:红太狼,husband:{name:灰太狼}}]);查询结果结构_id: ObjectId(64e1730b7867901a96e5f810) name: 狄如燕 husband: {name: 李元芳} _id: ObjectId(64e1730b7867901a96e5f811) name: 红太狼 husband: {name: 灰太狼}✅适用场景关联数据量小、查询总是一起访问、不需要独立更新。4.2 一对多关系文章与评论场景一篇文章对应多条评论。建模方案将评论数组直接嵌入文章文档。db.article.insertOne({name:《沉默的大多数》,comments:[很好看,给王小波点赞,神作]});// 为所有文章添加作者字段db.article.updateMany({name:{$exists:true}},{$set:{author:王小波}});// 插入第二篇文章db.article.insertOne({name:《祈念守护人》,author:东野圭吾,comments:[很治愈,好看,力推]});查询结果_id: ObjectId(...f825) name: 《沉默的大多数》 author: 王小波 comments: [很好看, 给王小波点赞, 神作] _id: ObjectId(...f827) name: 《祈念守护人》 author: 东野圭吾 comments: [很治愈, 好看, 力推]⚠️注意事项如果评论数量可能无限增长如微博评论不建议全部内嵌应考虑分页内嵌或单独集合引用。4.3 多对多关系老师与学生场景一个学生可以跟多个老师学习一个老师可以教多个学生。建模方案学生文档中通过teacher_ids数组存储关联老师的ObjectId。步骤1创建老师集合db.teachers.insertMany([{name:狄仁杰},{name:曾泰},{name:李元芳}]);步骤2创建学生集合db.students.insertMany([{name:狄春,age:22,isDel:0,gender:男},{name:狄如燕,age:20,isDel:0,gender:女}]);步骤3建立关联关系假设teachers集合中狄仁杰的_id为64e17b707867901a96e5f81e李元芳的_id为64e17b707867901a96e5f820db.students.updateOne({name:狄春},{$set:{teacher_ids:[ObjectId(64e17b707867901a96e5f81e),// 狄仁杰ObjectId(64e17b707867901a96e5f820)// 李元芳]}});查询结果_id: ObjectId(...f822) name: 狄春 age: 22 gender: 男 isDel: 0 teacher_ids: [ObjectId(64e17b70...81e), ObjectId(64e17b70...820)]关联查询MongoDB 3.2 支持$lookup聚合实现类似SQL的JOIN操作但性能不如内嵌文档建议根据查询模式选择建模方案。五、练习四条件查询进阶与$inc操作5.1 准备测试数据创建部门表和员工表db.dept.insertMany([{deptno:1001,dname:财务部,loc:北京},{deptno:1002,dname:办公室,loc:上海},{deptno:1003,dname:销售部,loc:成都},{deptno:1004,dname:运营部,loc:西安}]);db.emp.insertMany([{empno:7001,ename:光头强,job:伐木工,depno:1004,sal:800},{empno:7002,ename:熊大,job:护林员,depno:1004,sal:100},{empno:7003,ename:熊二,job:护林员,depno:1004,sal:500},{empno:7004,ename:吉吉,job:老板,depno:1001,sal:7000},{empno:7005,ename:毛毛,job:会计,depno:1002,sal:2000},{empno:7006,ename:二狗,job:保安,depno:1003,sal:1000},{empno:7007,ename:大马猴,job:保安,depno:1003,sal:1000}]);5.2 条件查询实战查询工资小于1100的员工db.emp.find({sal:{$lt:1100}});结果光头强(800)、熊大(100)、熊二(500)、二狗(1000)、大马猴(1000)查询工资在500-1000之间的员工db.emp.find({sal:{$gt:500,$lt:1000}});结果光头强(800)注意$gt: 500, $lt: 1000是开区间不包含边界值。若要包含边界请使用$gte和$lte。查询工资小于1000或大于2000的员工db.emp.find({$or:[{sal:{$lt:1000}},{sal:{$gt:2000}}]});结果光头强(800)、熊大(100)、熊二(500)、吉吉(7000)查询运营部所有员工// 先查运营部的deptnovardepnodb.dept.findOne({dname:运营部}).deptno;// 再用deptno查员工db.emp.find({depno:depno});结果光头强、熊大、熊二depno均为10045.3 $inc原子自增给所有工资低于等于1000的员工涨薪500元db.emp.updateMany({sal:{$lte:1000}},{$inc:{sal:500}});执行后光头强800 → 1300熊大100 → 600熊二500 → 1000二狗1000 → 1500大马猴1000 → 1500$inc 特性原子操作并发安全无需先读后写支持负数{$inc: {sal: -200}}实现减薪只能用于数值字段六、排序与投影精准控制输出6.1 sort排序// 默认按_id排序db.emp.find();// 按工资升序排序db.emp.find().sort({sal:1});// 按工资升序工资相同则按empno降序db.emp.find().sort({sal:1,empno:-1});值含义1升序 (Ascending)-1降序 (Descending)链式调用limit、skip、sort可以以任意顺序调用MongoDB会自动优化执行顺序。6.2 投影控制返回字段find()的第二个参数用于投影指定返回哪些字段// 只返回ename字段_id默认返回db.emp.find({},{ename:1});// 只返回ename不返回_iddb.emp.find({},{ename:1,_id:0});// 排除sal字段返回其他所有字段db.emp.find({},{sal:0});值含义1显示该字段0隐藏该字段⚠️规则投影中不能同时混用1和0_id除外。即要么指定要显示的字段其余隐藏要么指定要隐藏的字段其余显示。七、知识点速查表类别操作语法插入单条db.col.insertOne(doc)批量db.col.insertMany([doc1, doc2])查询等于{field: value}大于{field: {$gt: value}}小于等于{field: {$lte: value}}范围{field: {$gt: a, $lt: b}}OR条件{$or: [{cond1}, {cond2}]}更新修改字段{$set: {field: value}}删除字段{$unset: {field: 1}}原子自增{$inc: {field: n}}数组追加{$push: {field: value}}数组去重追加{$addToSet: {field: value}}整体替换db.col.replaceOne(filter, newDoc)删除单条db.col.deleteOne(filter)批量db.col.deleteMany(filter)分页限制数量.limit(n)跳过数量.skip(n)排序升序.sort({field: 1})降序.sort({field: -1})投影显示字段.find({}, {field: 1})隐藏字段.find({}, {field: 0})八、总结本文通过四个递进式练习系统覆盖了MongoDB CRUD的核心技能基础CRUDinsertOne/insertMany、find、updateOne/updateMany、deleteOne的基本用法数组与内嵌文档$pushvs$addToSet、点号查询hobby.sport、内嵌文档建模条件查询与分页比较运算符、$or、范围查询、limit/skip分页文档关系建模一对一内嵌、一对多数组内嵌、多对多ObjectId引用进阶操作$inc原子自增、sort排序、投影控制字段核心心法MongoDB的灵活性是双刃剑——模式自由让你快速迭代但也需要你在应用层保证数据一致性。选择合适的文档关系建模方案是MongoDB设计的关键。