MongoDB文档操作(增删改查)

449 阅读5分钟

持续创作,加速成长!这是我参与「掘金日新计划 · 10 月更文挑战」的第16天,点击查看活动详情

一、MongoDB文档操作

1-1、插入文档

3.2 版本之后新增了 db.collection.insertOne() 和 db.collection.insertMany()。

3.2版本之后建议使用db.collection.insertOne() 和 db.collection.insertMany()。

1-1-1、新增单个文档

  • insertOne: 支持writeConcern
db.collection.insertOne(
    <document>,
    {
        writeConcern: <document>
   }
)

image.png

writeConcern 决定一个写操作落到多少个节点上才算成功。writeConcern 的取值包括:

0:发起写操作,不关心是否成功;

1~集群最大数据节点数:写操作需要被复制到指定节点数才算成功;

majority:写操作需要被复制到大多数节点上才算成功。

  • insert: 若插入的数据主键已经存在,则会抛 DuplicateKeyException 异常,提示主键重复,不保存当前数据。
  • save: 如果 _id 主键存在则更新数据,如果不存在就插入数据。

image.png

1-1-2、批量新增文档

  • insertMany:向指定集合中插入多条文档数据
db.collection.insertMany(
        [ <document 1> , <document 2>, ... ],
        {
            writeConcern: <document>,
            ordered: <boolean>
        }
)

image.png

writeConcern:写入策略,默认为 1,即要求确认写操作,0 是不要求。

ordered:指定是否按顺序写入,默认 true,按顺序写入。

  • insert和save也可以实现批量插入

image.png

通过js脚本实现批量插入

  • 首先查看当前所在目录

image.png

  • 到对应目录创建脚本

编辑脚本book.js

var tags = ["nosql","mongodb","document","developer","popular"];
var types = ["technology","sociality","travel","novel","literature"];
var books=[];
for(var i=0;i<50;i++){
    var typeIdx = Math.floor(Math.random()*types.length);
    var tagIdx = Math.floor(Math.random()*tags.length);
    var favCount = Math.floor(Math.random()*100);
    var book = {
        title: "book-"+i,
        type: types[typeIdx],
        tag: tags[tagIdx],
        favCount: favCount,
        author: "xxx"+i
    };
    books.push(book)
}
db.books.insertMany(books);

进入mongo shell,执行

load("books.js")

image.png

1-2、查询文档

find 查询集合中的若干文档。语法格式如下:

db.collection.find(query, projection)
  • query :可选,使用查询操作符指定查询条件
  • projection :可选,使用投影操作符指定返回的键。查询时返回文档中所有键值, 只需省略该参数即可(默认省略)。投影时,_id为1的时候,其他字段必须是1;_id是0的时候,其他字段可以是0;如果没有_id字段约束,多个其他字段必须同为0或同为1。

通过指定条件查询 image.png

通过指定条件,并且设置返回字段

image.png

如果查询返回的条目数量较多,mongo shell则会自动实现分批显示。默认情况下每次只显示20条,可以输入it命令读取下一批。

findOne查询集合中的第一个文档。语法格式如下:

db.collection.findOne(query, projection)

image.png

如果你需要以易读的方式来读取数据,可以使用pretty)方法,语法格式如下:

db.collection.find().pretty()

注意:pretty()方法以格式化的方式来显示所有文档,上面查询是非格式化,下面为格式化之后的返回

image.png

1-2-1、条件查询

1-2-1-1、指定条件查询

#查询带有nosql标签的book文档:
db.books.find({tag:"nosql"})
#按照id查询单个book文档:
db.books.find({_id:ObjectId("61caa09ee0782536660494d9")})
#查询分类为“travel”、收藏数超过60个的book文档:
db.books.find({type:"travel",favCount:{$gt:60}})

image.png

使用条件查询,并且指定返回字段

image.png

1-2-1-2、查询条件对照表

SQLMQL
a = 1{a: 1}
a <> 1{a: {$ne: 1}}
a > 1{a: {$gt: 1}}
a >= 1{a: {$gte: 1}}
a < 1{a: {$lt: 1}}
a <= 1{a: {$lte: 1}}

1-2-1-3、查询逻辑对照表

SQLMQL
a = 1 AND b = 1{a: 1, b: 1}或{$and: [{a: 1}, {b: 1}]}
a = 1 OR b = 1{$or: [{a: 1}, {b: 1}]}
a IS NULL{a: {$exists: false}}
a IN (1, 2, 3){a: {$in: [1, 2, 3]}}

1-2-1-4、查询逻辑运算符

  • $lt: 存在并小于
  • $lte: 存在并小于等于
  • $gt: 存在并大于
  • $gte: 存在并大于等于
  • $ne: 不存在或存在但不等于
  • $in: 存在并在指定数组中
  • $nin: 不存在或不在指定数组中
  • $or: 匹配两个或多个条件中的一个
  • $and: 匹配全部条件

image.png

1-2-1-5、多级嵌套查询

直接使用外层属性.内层属性即可 image.png

1-2-2、排序&分页

1-2-2-1、指定排序

在 MongoDB 中使用 sort() 方法对数据进行排序

#指定按收藏数(favCount)降序返回 
db.books.find({type:"travel"}).sort({favCount:-1})
  • 1 为升序排列,而 -1 是用于降序排列

image.png

1-2-2-2、分页查询

skip用于指定跳过记录数,limit则用于限定返回结果数量。可以在执行find命令的同时指定skip、limit参数,以此实现分页的功能。比如,假定每页大小为8条,查询第3页的book文档:

db.books.find().skip(8).limit(4)

如下通过在skip中传入(页码-1)*每页数量,配合limit就可以完成分页查询

image.png

处理分页问题 – 巧分页 数据量大的时候,应该避免使用skip/limit形式的分页。

替代方案:使用查询条件+唯一排序条件;

例如:

第一页:db.posts.find({}).sort({_id: 1}).limit(20);

第二页:db.posts.find({_id: {$gt: <第一页最后一个_id>}}).sort({_id: 1}).limit(20);

第三页:db.posts.find({_id: {$gt: <第二页最后一个_id>}}).sort({_id: 1}).limit(20);

处理分页问题 – 避免使用 count 尽可能不要计算总页数,特别是数据量大和查询条件不能完整命中索引时。

考虑以下场景:假设集合总共有 1000w 条数据,在没有索引的情况下考虑以下查询:

db.coll.find({x: 100}).limit(50); 
db.coll.count({x: 100});
  • 前者只需要遍历前 n 条,直到找到 50 条 x=100 的文档即可结束;
  • 后者需要遍历完 1000w 条找到所有符合要求的文档才能得到结果。 为了计算总页数而进行的 count() 往往是拖慢页面整体加载速度的原因

1-2-2-3、正则表达式匹配查询

MongoDB 使用 $regex 操作符来设置匹配字符串的正则表达式。

//使用正则表达式查找type包含 so 字符串的book 
db.books.find({type:{$regex:"so"}}) 
//或者 
db.books.find({type:/so/})
  • 查询tag中包含go的数据
db.books.find({tag:/go/})

image.png

  • 查询tag以d开头的数据
db.books.find({tag:/^go/})

image.png

  • 查询tag以re结尾的数据
db.books.find({tag:/re$/})

image.png

  • 忽略大小写
db.books.find({type:/re/i})

image.png

1-3、更新文档

可以用update命令对指定的数据进行更新,命令的格式如下:

db.collection.update(query,update,options)
  • query:描述更新的查询条件;
  • update:描述更新的动作及新的内容;
  • options:描述更新的选项
    • upsert: 可选,如果不存在update的记录,是否插入新的记录。默认false,不插入
    • multi: 可选,是否按条件查询出的多条记录全部更新。 默认false,只更新找到的第一条记录
    • writeConcern :可选,决定一个写操作落到多少个节点上才算成功。

1-3-1、更新操作符

操作符格式描述
$set{$set:{field:value}}指定一个键并更新值,若键不存在则创建
$unset{$unset : {field : 1 }}删除一个键
$inc{$inc : {field : value } }对数值类型进行增减
$rename{$rename : {old_field_name : new_field_name } }修改字段名称
$push{ $push : {field : value } }将数值追加到数组中,若数组不存在则会进行初始化
$pushAll{$pushAll : {field : value_array }}追加多个值到一个数组字段内
$pull{$pull : {field : _value } }从数组中删除指定的元素
$addToSet{$addToSet : {field : value } }添加元素到数组中,具有排重功能
$pop{$pop : {field : 1 }}删除数组的第一个或最后一个元素

1-3-2、更新单个文档

某个book文档被收藏了,则需要将该文档的favCount字段自增--$inc

db.books.update({title:"book-17"},{$inc:{favCount:1}})

image.png

使用$set更新字段,如字段不存在则新增

image.png

使用$unset删除字段

image.png

1-3-3、更新多个文档

默认情况下,update命令只在更新第一个文档之后返回,如果需要更新多个文档,则可以使用multi选项。

将分类为“novel”的文档的增加发布时间(publishedDate)

db.books.update({type:"novel"},{$set:{publishedDate:new Date()}},{"multi":true})

multi : 可选,mongodb 默认是false,只更新找到的第一条记录,如果这个参数为true,就把按条件查出来多条记录全部更新

image.png

update命令的选项配置较多,为了简化使用还可以使用一些快捷命令:

  • updateOne:更新单个文档。

image.png

  • updateMany:更新多个文档。 image.png
  • replaceOne:替换单个文档。

替换前type:travel有五条数据,通过replaceOne替换之后type:travel就剩下4条

image.png

替换后的数据会把整个文档全部替换,如下:

image.png

1-3-3-1、使用upsert命令

upsert是一种特殊的更新,其表现为如果目标文档不存在,则执行插入命令。

db.books.update(
    {title:"my book"},
    {$set:{tags:["nosql","mongodb"],type:"none",author:"fox"}},
    {upsert:true}
)

nMatched、nModified都为0,表示没有文档被匹配及更新,nUpserted=1提示执行了upsert动作

首先执行update,查询是没有数据的,然后再使用upsert:true,则提示nUpserted:1代表没有更新数据,进行了插入操作 image.png

查询数据如下:

image.png

1-3-3-2、实现replace语义

update命令中的更新描述(update)通常由操作符描述,如果更新描述中不包含任何操作符,那么MongoDB会实现文档的replace语义

db.books.update(
    {title:"my book"},
    {justTitle:"my first book"}
)    

image.png

1-3-3-3、findAndModify命令

findAndModify兼容了查询和修改指定文档的功能,findAndModify只能更新单个文档

//将某个book文档的收藏数(favCount)加1
db.books.findAndModify({
    query:{_id:ObjectId("61caa09ee0782536660494dd")},
    update:{$inc:{favCount:1}}
})  

该操作会返回符合查询条件的文档数据,并完成对文档的修改(此时返回的是修改前文档)。

image.png

默认情况下,findAndModify会返回修改前的“旧”数据。如果希望返回修改后的数据,则可以指定new选项

db.books.findAndModify({
    query:{_id:ObjectId("61caa09ee0782536660494dd")},
    update:{$inc:{favCount:1}},
    new: true
}) 

image.png

与findAndModify语义相近的命令如下:

  • findOneAndUpdate:更新单个文档并返回更新前(或更新后)的文档。
  • findOneAndReplace:替换单个文档并返回替换前(或替换后)的文档。

1-4、删除文档

1-4-1、使用 remove 删除文档

  • remove 命令需要配合查询条件使用;
  • 匹配查询条件的文档会被删除;
  • 指定一个空文档条件会删除所有文档;

示例:

db.user.remove({age:28})// 删除age 等于28的记录
db.user.remove({age:{$lt:25}})   // 删除age 小于25的记录
db.user.remove( { } ) // 删除所有记录
db.user.remove() //报错   

remove命令会删除匹配条件的全部文档,如果希望明确限定只删除一个文档,则需要指定justOne参数,命令格式如下:

db.collection.remove(query,justOne)

例如:删除满足type:novel条件的首条记录

db.books.remove({type:"novel"},true)

image.png

1-4-2、使用 delete 删除文档

官方推荐使用 deleteOne() 和 deleteMany() 方法删除文档,语法格式如下:

db.books.deleteMany ({})  //删除集合下全部文档
db.books.deleteMany ({ type:"travel" })  //删除 type等于 novel 的全部文档
db.books.deleteOne ({ type:"travel" })  //删除 type等于novel 的一个文档  

image.png

注意: remove、deleteMany等命令需要对查询范围内的文档逐个删除,如果希望删除整个集合,则使用drop命令会更加高效

1-4-3、返回被删除文档

remove、deleteOne等命令在删除文档后只会返回确认性的信息,如果希望获得被删除的文档,则可以使用findOneAndDelete命令

db.books.findOneAndDelete({type:"travel"})

image.png

除了在结果中返回删除文档,findOneAndDelete命令还允许定义“删除的顺序”,即按照指定顺序删除找到的第一个文档

db.books.findOneAndDelete({type:"novel"},{sort:{favCount:1}})

remove、deleteOne等命令只能按默认顺序删除,利用这个特性,findOneAndDelete可以实现队列的先进先出。

文档操作最佳实践

关于文档结构

  • 防止使用太长的字段名(浪费空间)
  • 防止使用太深的数组嵌套(超过2层操作比较复杂)
  • 不使用中文,标点符号等非拉丁字母作为字段名

关于写操作

  • update 语句里只包括需要更新的字段
  • 尽可能使用批量插入来提升写入性能
  • 使用TTL自动过期日志类型的数据