MongoDB 索引和常用命令

一、基本常用命令

1.1 案例需求

        存放文章评论的数据存放到 MongoDB 中,数据结构参考如下,其中数据库为 articledb

专栏文章评论

comment

字段名称

字段含义

字段类型

备注

_id

ID

ObjectId或String

Mongo的主键的字段

articleid

文章ID

String

content

评论内容

String

userid

评论人ID

String

nickname

评论人昵称

String

createdatetime

评论的日期时间

Date

likenum

点赞数

Int32

replynum

回复数

Int32

state

状态

String

0:不可见;1:可见;

parentid

上级ID

String

如果为0表示文章的顶级评论

1.2 数据库操作

1.2.1 选择和创建数据库

        在 MongoDB 中,集合只有在内容插入后才会创建。 就是说,创建集合(数据表)后要再插入一个文档(记录),集合才会真正创建。

# 选择和创建数据库的语法格式
# 数据库名可以是满足以下条件的任意UTF-8字符串。
#     不能是空字符串("")
#     不得含有' '(空格)、.、$、/、\和\0 (空字符)
#     应全部小写
#     最多64字节
use 数据库名称# 如果数据库不存在则自动创建,例如,以下语句创建 spitdb 数据库
use articledb# 查看有权限查看的所有的数据库命令
show dbs 或 show databases# 查看正在使用的数据库命令
# MongoDB 中默认的数据库为 test,如果你没有选择数据库,集合将存放在 test 数据库中。
db

        有一些数据库名是保留的,可以直接访问这些有特殊作用的数据库,如下

        admin: 从权限的角度来看,这是 "root" 数据库。要是将一个用户添加到这个数据库,这个用户自动继承所有数据库的权限。一些特定的服务器端命令也只能从这个数据库运行,比如列出所有的数据库或者关闭服务器。

        local: 这个数据永远不会被复制,可以用来存储限于本地单台服务器的任意集合

        config: 当 Mongo 用于分片设置时,config 数据库在内部使用,用于保存分片的相关信息。

1.2.2 数据库的删除

# 会删除当前正在使用的库,主要用来删除已经持久化的数据库
db.dropDatabase()

1.3 集合操作

        集合,类似关系型数据库中的表。可以显示的创建,也可以隐式的创建。

1.3.1 集合的显示创建

# 集合的命名规范
#     集合名不能是空字符串""。
#     集合名不能含有\0字符(空字符),这个字符表示集合名的结尾。
#     集合名不能以"system."开头,这是为系统集合保留的前缀。
#     用户创建的集合名字不能含有保留字符。有些驱动程序的确支持在集合名里面包含,这是因为某些系统生成的集合中包含该字符。除非你要访问这种系统创建的集合,否则千万不要在名字里出现$。# 基本语法格式如下
# name: 要创建的集合名称
db.createCollection(name)# 创建一个名为 mycollection 的普通集合
db.createCollection("mycollection")# 查看当前库中的表
show collections 或 show tables

1.3.2 集合的隐式创建

        当向一个集合中插入一个文档的时候,如果集合不存在,则会自动创建集合。通常我们使用隐式创建文档即可。

1.3.3 集合的删除

# 语法格式如下
db.collection.drop() 或 db.集合.drop()# 要删除 mycollection 集合,
# 如果成功删除选定集合,则 drop() 方法返回 true,否则返回 false
db.mycollection.drop()

1.4 文档操作

        文档(document)的数据结构和 JSON 基本一样。所有存储在集合中的数据都是 BSON 格式。

1.4.1 文档的插入

1):单个文档的插入

        使用 insert() save() 方法向集合中插入文档,语法如下:

db.collection.insert(<document or array of documents>,{writeConcern: <document>,ordered: <boolean>}
)

Parameter

Type

Description

document

document or array

要插入到集合中的文档或文档数组。((json格式)

writeConcern

document

插入时性能和可靠性的级别,了解即可。

ordered

boolean

可选。如果为真,则按顺序插入数组中的文档,如果其中一个文档出现错误,MongoDB将返回而不处理数组中的其余文档。如果为假,则执行无序插入,如果其中一个文档出现错误,则继续处理数组中的主文档。在版本2.6+中默认为true

        接下来我们要向 comment 的集合(表)中插入一条测试数据,执行下面语句:

# 示例代码
db.comment.insert({"articleid":"100000","content":"今天天气真好,阳光明媚","userid":"1001","nickname":"Rose","createdatetime":new Date(),"likenum":NumberInt(10),"state":null}
)

提示: 

        1)comment 集合如果不存在,则会隐式创建。

        2)mongo 中的数字,默认情况下是 double 类型,如果要存整型,必须使用函数NumberInt(整型数字),否则取出来就有问题了。

        3)插入当前日期使用 new Date()

        4)插入的数据没有指定 _id ,会自动生成主键值

        5)如果某字段没值,可以赋值为 null,或不写该字段。

        出现下面的内容就证明插入成功了

注意:

        1. 文档中的键/值对是有序的。

        2. 文档中的值不仅可以是在双引号里面的字符串,还可以是其他几种数据类型(甚至可以是整个嵌入的文档)。

        3. MongoDB 区分类型和大小写。

        4. MongoDB 的文档不能有重复的键。

        5. 文档的键是字符串。除了少数例外情况,键可以使用任意 UTF-8 字符。 

文档键命名规范:

        1、键不能含有\0 (空字符)。这个字符用来表示键的结尾。

        2、.和$有特别的意义,只有在特定环境下才能使用。

        3、以下划线 "_" 开头的键是保留的(不是严格要求的)。

2):多个文档的插入

   使用 insertMany() 方法向集合中插入多个文档,语法如下:

db.collection.insertMany([ <document 1> , <document 2>, ... ],{writeConcern: <document>,ordered: <boolean>}
)

        接下来我们要向 comment 的集合(表)中插入多条文章评论,执行下面语句:

db.comment.insertMany([{"_id":"1","articleid":"100001","content":"我们不应该把清晨浪费在手机上,健康很重要,一杯温水幸福你我他。","userid":"1002","nickname":"相忘于江湖","createdatetime":new Date("2019-08-05T22:08:15.522Z"),"likenum":NumberInt(1000),"state":"1"},{"_id":"2","articleid":"100001","content":"我夏天空腹喝凉开水,冬天喝温开水","userid":"1005","nickname":"伊人憔悴","createdatetime":new Date("2019-08-05T23:58:51.485Z"),"likenum":NumberInt(888),"state":"1"},{"_id":"3","articleid":"100001","content":"我一直喝凉开水,冬天夏天都喝。","userid":"1004","nickname":"杰克船长","createdatetime":new Date("2019-08-06T01:05:06.321Z"),"likenum":NumberInt(666),"state":"1"},{"_id":"4","articleid":"100001","content":"专家说不能空腹吃饭,影响健康。","userid":"1003","nickname":"凯撒","createdatetime":new Date("2019-08-06T08:18:35.288Z"),"likenum":NumberInt(2000),"state":"1"},{"_id":"5","articleid":"100001","content":"研究表明,刚烧开的水千万不能喝,因为烫嘴。","userid":"1003","nickname":"凯撒","createdatetime":new Date("2019-08-06T11:01:02.521Z"),"likenum":NumberInt(3000),"state":"1"}
]);

提示:

        如果插入时指定了 _id ,则主键就是该值。如果某条数据插入失败,将会终止插入,但已经插入成功的数据不会回滚掉。

        因为批量插入由于数据较多容易出现失败,因此,可以使用 try catch 进行异常捕捉处理,测试的时候可以不处理。如(了解):

try {db.comment.insertMany([{"_id":"1","articleid":"100001","content":"我们不应该把清晨浪费在手机上,健康很重要,一杯温水幸福你我他。","userid":"1002","nickname":"相忘于江湖","createdatetime":new Date("2019-08-05T22:08:15.522Z"),"likenum":NumberInt(1000),"state":"1"},{"_id":"2","articleid":"100001","content":"我夏天空腹喝凉开水,冬天喝温开水","userid":"1005","nickname":"伊人憔悴","createdatetime":new Date("2019-08-05T23:58:51.485Z"),"likenum":NumberInt(888),"state":"1"},{"_id":"3","articleid":"100001","content":"我一直喝凉开水,冬天夏天都喝。","userid":"1004","nickname":"杰克船长","createdatetime":new Date("2019-08-06T01:05:06.321Z"),"likenum":NumberInt(666),"state":"1"},{"_id":"4","articleid":"100001","content":"专家说不能空腹吃饭,影响健康。","userid":"1003","nickname":"凯撒","createdatetime":new Date("2019-08-06T08:18:35.288Z"),"likenum":NumberInt(2000),"state":"1"},{"_id":"5","articleid":"100001","content":"研究表明,刚烧开的水千万不能喝,因为烫嘴。","userid":"1003","nickname":"凯撒","createdatetime":new Date("2019-08-06T11:01:02.521Z"),"likenum":NumberInt(3000),"state":"1"}]);
} catch (e) {print (e);
}

1.4.2 文档的查询

# 查询数据的语法格式如下:
db.collection.find(<query>, [projection])

Parameter

Type

Description

query

document

可选。使用查询运算符指定选择筛选器。若要返回集合中的所有文档,请省略此参数或传递空文档( {} )。

projection

document

可选。指定要在与查询筛选器匹配的文档中返回的字段(投影)。若要返回匹配文档中的所有字段,请省略此参数。

1)查询所有

db.comment.find()
或
db.comment.find({})

        这里你会发现每条文档会有一个叫 _id 的字段,这个相当于我们原来关系数据库中表的主键,当你在插入文档记录时没有指定该字段,MongoDB 会自动创建,其类型是 ObjectID 类型。

        如果我们在插入文档记录时指定该字段也可以,其类型可以是 ObjectID 类型,也可以是MongoDB 支持的任意类型。 

        如果我想按一定条件来查询,比如我想查询 userid 1003 的记录,怎么办?很简单!只要在 find() 中添加参数即可,参数也是 json 格式,如下:

# 查询指定条件的数据
db.comment.find({userid:'1003'})

        如果你只需要返回符合条件的第一条数据,我们可以使用 findOne 命令来实现,语法和 find 一样。

# 查询用户编号是 1003 的记录,但只最多返回符合条件的第一条记录:
db.comment.findOne({userid:'1003'})

2)投影查询

        如果要查询结果返回部分字段,则需要使用投影查询(不显示所有字段,只显示指定的字段),其中 _id 字段会默认显示。

# 查询结果只显示 _id、userid、nickname 字段
db.comment.find({userid:"1003"},{userid:1,nickname:1})

# 查询结果只显示 、userid、nickname ,不显示 _id
db.comment.find({userid:"1003"},{userid:1,nickname:1,_id:0})

# 查询所有数据,但只显示 _id、userid、nickname
db.comment.find({},{userid:1,nickname:1})

1.4.3 文档的更新

        更新文档的语法如下:

db.collection.update(query, update, options)//或
db.collection.update(<query>,<update>,{upsert: <boolean>,multi: <boolean>,writeConcern: <document>,collation: <document>,arrayFilters: [ <filterdocument1>, ... ],hint: <document|string> // Available starting in MongoDB 4.2}
)

        只需要关注前四个参数就可以了 

Parameter

Type

Description

query

document

更新的选择条件。可以使用与 find() 方法中相同的查询选择器,类似 sql update 查询内 where 后面的。在 3.0 版中进行了更改:当使用 upsert:true 执行 update() 时,如果查询使用点表示法在 _id 字段上指定条件,则 MongoDB 将拒绝插入新文档。

update

document or pipeline要应用的修改。该值可以是:包含更新运算符表达式的文档,或仅包含:对的替换文档

upsert

boolean

可选。如果设置为 true,则在没有与查询条件匹配的文档时创建新文档。默认值为 false,如果找不到匹配项,则不会插入新文档。

multi

boolean

可选。如果设置为 true,则更新符合查询条件的多个文档。如果设置为 false,则更新一个文档。默认值为 false。

writeConcern

document

可选。表示写问题的文档。抛出异常的级别。

collation

document

可选。指定要用于操作的校对规则。校对规则允许用户为字符串比较指定特定于语言的规则,例如字母大小写和重音标记的规则。

arrayFilters

array

可选。一个筛选文档数组,用于确定要为数组字段上的更新操作修改哪些数组元素。

hint

Document or string

可选。指定用于支持查询谓词的索引的文档或字符串。该选项可以采用索引规范文档或索引名称字符串。如果指定的索引不存在,则说明操作错误。例如,请参阅版本4中的“为更新操作指定提示。

1):覆盖的修改

# 修改_id为1的记录,点赞量为1001
db.comment.update({_id:"1"},{likenum:NumberInt(1001)})

        执行完上面的语句后,我们会发现,这条文档除了 likenum 字段其它字段都不见了 

2):局部修改

        为了解决这个问题,我们需要使用修改器 $set 来实现,命令如下 

# 修改 _id 为 2 的记录,浏览量为 889
db.comment.update({_id:"2"},{$set:{likenum:NumberInt(889)}})

        这样执行就可以更新指定的字段了,如下所示 

3):批量的修改

# 默认只修改第一条数据
db.comment.update({userid:"1003"},{$set:{nickname:"凯撒2"}})# 修改所有符合条件的数据
db.comment.update({userid:"1003"},{$set:{nickname:"凯撒大帝"}},{multi:true})

4):列值增长的修改

        如果我们想实现对某列值在原有值的基础上进行增加或减少,可以使用 $inc 运算符来实现。

# 对3号数据的点赞数,每次递增1
db.comment.update({_id:"3"},{$inc:{likenum:NumberInt(1)}})

1.4.4 文档的删除

# 删除文档的语法结构
db.集合名称.remove(条件)# 将数据全部删除,请慎用
db.comment.remove({})# 删除_id=1的记录,输入以下语句
db.comment.remove({_id:"1"})

1.5 文档的分页查询

1.5.1 统计查询

        统计查询使用 count() 方法,语法如下:

# 可选项 options 暂时不使用
# 默认情况下 count() 方法返回符合条件的全部记录条数
db.collection.count(query, options)

Parameter

Type

Description

query

document

查询选择条件。

options

document

可选。用于修改计数的额外选项。

1):统计所有记录数

# 统计 comment 集合的所有的记录数
db.comment.count()

2):按条件统计记录数

# 统计 userid 为 1003 的记录条数
db.comment.count({userid:"1003"})

1.5.2 分页列表查询

        可以使用 limit() 方法来读取指定数量的数据,使用 skip() 方法来跳过指定数量的数据。基本语法如下所示:

db.COLLECTION_NAME.find().limit(NUMBER).skip(NUMBER)

        如果你想返回指定条数的记录,可以在 find() 方法后调用 limit 来返回结果( TopN),默认值是 20,例如: 

# 返回 3 条记录
db.comment.find().limit(3)

        skip() 方法同样接受一个数字参数作为跳过的记录条数。(前 N 个不要),默认值是 0

# 跳过前 3 条记录
db.comment.find().skip(3)

        分页查询,每页显示 2 条数据,第二页显示第 3 和第 4 条数据。

# 第一页
db.comment.find().skip(0).limit(2)
# 第二页
db.comment.find().skip(2).limit(2)
# 第三页
db.comment.find().skip(4).limit(2)

1.5.3 排序查询

        sort() 方法对数据进行排序,sort() 方法可以通过参数指定排序的字段,并使用 1 -1 来指定排序的方式,其中 1 为升序排列,而 -1 是用于降序排列。语法如下所示:

db.COLLECTION_NAME.find().sort({KEY:1})
或
db.集合名称.find().sort(排序方式)
# 对 userid 降序排列,并对访问量进行升序排列
db.comment.find().sort({userid:-1,likenum:1})

提示:

        skip()limilt(), sort() 三个方法放在一起执行的时候,执行的顺序是先 sort(),然后是 skip(),最后是显示的 limit(),和命令编写顺序无关。 

1.6 文档的更多查询

1.6.1 复杂条件查询

        MongoDB 的模糊查询是通过正则表达式的方式实现的。格式为:

# 正则表达式是 js 的语法,直接量的写法
db.collection.find({field:/正则表达式/})
或 
db.集合.find({字段:/正则表达式/})
# 查询评论内容包含 “开水” 的所有文档
db.comment.find({content:/开水/})# 要查询评论的内容中以 “专家” 开头的
db.comment.find({content:/^专家/})

1.6.2 比较查询

# 大于: field > value
db.集合名称.find({ "field" : { $gt: value }}) # 小于:field < value
db.集合名称.find({ "field" : { $lt: value }}) # 大于等于: field >= value
db.集合名称.find({ "field" : { $gte: value }}) # 小于等于: field <= value
db.集合名称.find({ "field" : { $lte: value }}) # 不等于: field != value
db.集合名称.find({ "field" : { $ne: value }}) 
# :查询评论点赞数量大于 700 的记录
db.comment.find({likenum:{$gt:NumberInt(700)}})

1.6.3 包含查询

        包含使用 $in 操作符,不包含使用 $nin 操作符

# 查询评论的集合中 userid 字段包含 1003 或 1004 的文档
db.comment.find({userid:{$in:["1003","1004"]}})# 查询评论集合中 userid 字段不包含 1003 和 1004 的文档
db.comment.find({userid:{$nin:["1003","1004"]}})

1.6.4 条件查询

        我们如果需要查询同时满足两个以上条件,需要使用 $and 操作符将条件进行关联。(相当于 SQL and)格式如下:

$and:[ { },{ },{ } ]
# 查询评论集合中likenum大于等于700 并且小于2000的文档
db.comment.find({$and:[{likenum:{$gte:NumberInt(700)}},{likenum:{$lt:NumberInt(2000)}}]})

        如果两个以上条件之间是或者的关系,我们使用 $or 操作符进行关联,与前面 and 的使用方式相同格式为:

$or:[ { },{ },{ } ]
# 查询评论集合中 userid 为 1003,或者点赞数小于 1000 的文档记录
db.comment.find({$or:[ {userid:"1003"} ,{likenum:{$lt:1000} }]})

1.7 常用命令小节

# 选择切换数据库
use articledb# 插入数据
db.comment.insert({bson数据})# 查询所有数据
db.comment.find()# 条件查询数据
db.comment.find({条件})# 查询符合条件的第一条记录
db.comment.findOne({条件})# 查询符合条件的前几条记录
db.comment.find({条件}).limit(条数)# 查询符合条件的跳过的记录
db.comment.find({条件}).skip(条数)# 修改数据
db.comment.update({条件},{修改后的数据}) 或 db.comment.update({条件},{$set:{要修改部分的字段:数据})# 数据并自增某字段值
db.comment.update({条件},{$inc:{自增的字段:步进值}})# 删除数据
db.comment.remove({条件})# 统计查询
db.comment.count({条件})# 模糊查询
db.comment.find({字段名:/正则表达式/})# 条件比较运算
db.comment.find({字段名:{$gt:值}})# 包含查询
db.comment.find({字段名:{$in:[值1,值2]}}) 或 db.comment.find({字段名:{$nin:[值1,值2]}})# 条件连接查询
db.comment.find({$and:[{条件1},{条件2}]}) 或 db.comment.find({$or:[{条件1},{条件2}]})

二、索引

2.1 概述

        在 MongoDB 中使用索引可以高效地执行查询。如果没有索引,MongoDB 必须执行全集合扫描,即扫描集合中的每个文档,以选择与查询语句匹配的文档。这种扫描全集合的查询效率是非常低的,特别在处理大量的数据时,查询可以要花费几十秒甚至几分钟,这对网站的性能是非常致命的。

        如果在查询时存在适当的索引,MongoDB 可以使用该索引限制必须检查的文档数。

        索引是特殊的数据结构,它以易于遍历的形式存储集合数据集的一小部分。索引存储特定字段或一组字段的值,按字段值排序。索引项的排序支持有效的相等匹配和基于范围的查询操作。此外,MongoDB 还可以使用索引中的排序返回排序结果。

        MongoDB 索引使用 B 树数据结构(确切的说是 B-TreeMySQL B+Tree

2.2 索引的类型

2.2.1 单字段索引

        MongoDB 支持在文档的单个字段上创建用户定义的升序/降序索引,称为单字段索引。

        对于单个字段索引和排序操作,索引键的排序顺序(即升序或降序)并不重要,因为 MongoDB 可以在任何方向上遍历索引。

2.2.2 符合索引

        MongoDB 还支持多个字段的用户定义索引,即复合索引。

        复合索引中列出的字段顺序具有重要意义。例如,如果复合索引由 { userid: 1, score: -1 } 组成,则索引首先按 userid 正序排序,然后在每个 userid 的值内,再在按 score 倒序排序。

2.2.3 其他索引

1):地理空间索引(Geospatial Index)

        为了支持对地理空间坐标数据的有效查询,MongoDB 提供了两种特殊的索引:返回结果时使用平面几何的二维索引和返回结果时使用球面几何的二维球面索引。

2:)文本索引(Text Indexes)

        MongoDB 提供了一种文本索引类型,支持在集合中搜索字符串内容。这些文本索引不存储特定于语言的停止词(例如 “the”、“a”、“or”),而将集合中的词作为词干,只存储根词。

3:)哈希索引(Hashed Indexes)

        为了支持基于散列的分片,MongoDB 提供了散列索引类型,它对字段值的散列进行索引。这些索引在其范围内的值分布更加随机,但只支持相等匹配,不支持基于范围的查询。

2.3 索引的操作

2.3.1 查看索引

# 返回一个集合中的所有索引的数组
# MongoDB 3.0+ 版本以上支持
db.collection.getIndexes()
# 查看 comment 集合中所有的索引情况
> db.comment.getIndexes()
[{"v" : 2,"key" : {"_id" : 1},"name" : "_id_","ns" : "test.comment"}
]

        结果中显示的是默认 _id 索引。

        默认 _id 索引:MongoDB 在创建集合的过程中,在 _id 字段上创建一个唯一的索引,默认名字为 _id_ ,该索引可防止客户端插入两个具有相同值的文档,您不能在 _id 字段上删除此索引。

        注意:该索引是唯一索引,因此值不能重复,即 _id 值不能重复的。在分片集群中,通常使用 _id 作为片键。 

2.3.2 创建索引

# 在集合上创建索引
db.collection.createIndex(keys, options)

Parameter

Type

Description

keys

document

包含字段和值对的文档,其中字段是索引键,值描述该字段的索引类型。对于字段上的升序索引,请指定值1;对于降序索引,请指定值-1。比如: {字段:1或-1} ,其中1 为指定按升序创建索引,如果你想按降序来创建索引指定为 -1 即可。另外,MongoDB 支持几种不同的索引类型,包括文本、地理空间和哈希索引。

options

document

可选。包含一组控制索引创建的选项的文档。有关详细信息,请参见选项详情列表。

        options(更多选项)列表

Parameter

Type

Description

background

Boolean

建索引过程会阻塞其它数据库操作,background 可指定以后台方式创建索引,即增加 "background" 可选参数。 "background" 默认值为false。

unique

Boolean

建立的索引是否唯一。指定为 true 创建唯一索引。默认值为false.

name

string

索引的名称。如果未指定,MongoDB 的通过连接索引的字段名和排序顺序生成一个索引名称。

dropDups

Boolean3.0+ 版本已废弃。在建立唯一索引时是否删除重复记录,指定 true 创建唯一索引。默认值为 false

sparse

Boolean对文档中不存在的字段数据不启用索引;这个参数需要特别注意,如果设置为 true 的话,在索引字段中不会查询出不包含对应字段的文档.。默认值为 false

expireAfterSeconds

integer

指定一个以秒为单位的数值,完成 TTL 设定,设定集合的生存时间。

v

index version

索引的版本号。默认的索引版本取决于 mongod 创建索引时运行的版本。

weights

document

索引权重值,数值在 1 到 99,999 之间,表示该索引相对于其他索引字段的得分权重

default_language

string

对于文本索引,该参数决定了停用词及词干和词器的规则的列表。 默认为英语

language_override

string

对于文本索引,该参数指定了包含在文档中的字段名,语言覆盖默认的language,默认值为 language

提示:

        注意在 3.0.0 版本前创建索引方法为 db.collection.ensureIndex() ,之后的版本使用了 db.collection.createIndex() 方法, ensureIndex()  还能用,但只是 createIndex() 的别名。

# 单字段索引示例,对 userid 字段按升序建立索引
> db.comment.createIndex({userid:1})
{"createdCollectionAutomatically" : false,"numIndexesBefore" : 1,"numIndexesAfter" : 2,"ok" : 1
}
# 查看刚才创建的索引,新创建的索引名称为 userid_1
> db.comment.getIndexes()
[{"v" : 2,"key" : {"_id" : 1},"name" : "_id_","ns" : "test.comment"},{"v" : 2,"key" : {"userid" : 1},"name" : "userid_1","ns" : "test.comment"}
]
# 复合索引示例,对 userid 和 nickname 同时按降序建立复合索引
> db.comment.createIndex({userid:1,nickname:-1})
{"createdCollectionAutomatically" : false,"numIndexesBefore" : 2,"numIndexesAfter" : 3,"ok" : 1
}
# 查看刚才创建的索引,新创建的索引名称为 userid_1_nickname_-1
> db.comment.getIndexes()
[{"v" : 2,"key" : {"_id" : 1},"name" : "_id_","ns" : "test.comment"},{"v" : 2,"key" : {"userid" : 1},"name" : "userid_1","ns" : "test.comment"},{"v" : 2,"key" : {"userid" : 1,"nickname" : -1},"name" : "userid_1_nickname_-1","ns" : "test.comment"}
]

2.3.3 删除索引

        可以移除指定的索引,或移除所有索引

# 指定索引的移除
db.collection.dropIndex(index)# 所有索引的移除
db.collection.dropIndexes()

Parameter

Type

Description

index

string or document指定要删除的索引。可以通过索引名称或索引规范文档指定索引。若要删除文本索引,请指定索引名称。
# 删除 comment 集合中 userid 字段上的升序索引
> db.comment.dropIndex({userid:1})
{ "nIndexesWas" : 3, "ok" : 1 }# 查看是否删除成功
> db.comment.getIndexes()
[{"v" : 2,"key" : {"_id" : 1},"name" : "_id_","ns" : "test.comment"},{"v" : 2,"key" : {"userid" : 1,"nickname" : -1},"name" : "userid_1_nickname_-1","ns" : "test.comment"}
]
# 删除 comment 集合中所有索引。
> db.comment.dropIndexes()
{"nIndexesWas" : 2,"msg" : "non-_id indexes dropped for collection","ok" : 1
}
# 查看还剩下哪些索引
# _id 的字段的索引是无法删除的,只能删除非 _id 字段的索引。
> db.comment.getIndexes()
[{"v" : 2,"key" : {"_id" : 1},"name" : "_id_","ns" : "test.comment"}
]

2.4 索引的使用

2.4.1 执行计划

        分析查询性能(Analyze Query Performance)通常使用执行计划(解释计划、Explain Plan)来查看查询的情况,如查询耗费的时间、是否基于索引查询等。

        那么,通常,我们想知道,建立的索引是否有效,效果如何,都需要通过执行计划查看。语法如下:

db.collection.find(query,options).explain(options)
# 查看根据 userid 查询数据的情况
# 其中:"stage" : "COLLSCAN", 表示全集合扫描
> db.comment.find({userid:"1003"}).explain()
{"queryPlanner" : {"plannerVersion" : 1,"namespace" : "test.comment","indexFilterSet" : false,"parsedQuery" : {"userid" : {"$eq" : "1003"}},"winningPlan" : {"stage" : "COLLSCAN","filter" : {"userid" : {"$eq" : "1003"}},"direction" : "forward"},"rejectedPlans" : [ ]},"serverInfo" : {"host" : "node1","port" : 27017,"version" : "4.0.28","gitVersion" : "af1a9dc12adcfa83cc19571cb3faba26eeddac92"},"ok" : 1
}
>
# 下面对userid建立索引
>  db.comment.createIndex({userid:1})
{"createdCollectionAutomatically" : false,"numIndexesBefore" : 1,"numIndexesAfter" : 2,"ok" : 1
}
# 再次查看执行计划
# 其中:"stage" : "FETCH" 表示基于索引的扫描
> db.comment.find({userid:"1003"}).explain()
{"queryPlanner" : {"plannerVersion" : 1,"namespace" : "test.comment","indexFilterSet" : false,"parsedQuery" : {"userid" : {"$eq" : "1003"}},"winningPlan" : {"stage" : "FETCH","inputStage" : {"stage" : "IXSCAN","keyPattern" : {"userid" : 1},"indexName" : "userid_1","isMultiKey" : false,"multiKeyPaths" : {"userid" : [ ]},"isUnique" : false,"isSparse" : false,"isPartial" : false,"indexVersion" : 2,"direction" : "forward","indexBounds" : {"userid" : ["[\"1003\", \"1003\"]"]}}},"rejectedPlans" : [ ]},"serverInfo" : {"host" : "node1","port" : 27017,"version" : "4.0.28","gitVersion" : "af1a9dc12adcfa83cc19571cb3faba26eeddac92"},"ok" : 1
}

2.4.2 涵盖查询

        当查询条件和查询的投影仅包含索引字段时,MongoDB 直接从索引返回结果,而不扫描任何文档或将文档带入内存。 这些覆盖的查询可以非常有效。

# 只查询 userid 字段
> db.comment.find({userid:"1003"},{userid:1,_id:0})
{ "userid" : "1003" }
{ "userid" : "1003" }
> db.comment.find({userid:"1003"},{userid:1,_id:0}).explain()
{"queryPlanner" : {"plannerVersion" : 1,"namespace" : "test.comment","indexFilterSet" : false,"parsedQuery" : {"userid" : {"$eq" : "1003"}},"winningPlan" : {"stage" : "PROJECTION","transformBy" : {"userid" : 1,"_id" : 0},"inputStage" : {"stage" : "IXSCAN","keyPattern" : {"userid" : 1},"indexName" : "userid_1","isMultiKey" : false,"multiKeyPaths" : {"userid" : [ ]},"isUnique" : false,"isSparse" : false,"isPartial" : false,"indexVersion" : 2,"direction" : "forward","indexBounds" : {"userid" : ["[\"1003\", \"1003\"]"]}}},"rejectedPlans" : [ ]},"serverInfo" : {"host" : "node1","port" : 27017,"version" : "4.0.28","gitVersion" : "af1a9dc12adcfa83cc19571cb3faba26eeddac92"},"ok" : 1
}

三、实战演练

3.1 需求分析

        某头条的文章评论业务如下:

参考这篇文章实现以下功能

        1):基本增删改查 API

        2):根据文章 id 查询评论

        3):评论点赞

3.2 表结构分析

        数据库:articledb

专栏文章评论

comment

字段名称

字段含义

字段类型

备注

_id

ID

ObjectId 或 String

Mongo的主键的字段

articleid

文章ID

String

content

评论内容

String

userid

评论人ID

String

nickname

评论人昵称

String

createdatetime

评论的日期时间

Date

likenum

点赞数

Int32

replynum

回复数

Int32

state

状态

String

0:不可见;1:可见;

parentid

上级ID

String

如果为0表示文章的顶级评论

3.3 技术选型

3.3.1 mongodb-driver

        mongodb-driver mongo 官方推出的 java 连接 mongoDB 的驱动包,相当于 JDBC 驱动。

3.3.2 SpringDataMongoDB

        SpringData 家族成员之一,用于操作 MongoDB 的持久层框架,封装了底层的 mongodb-driver。我们此次使用这个工具进行开发。

3.4 项目搭建

3.4.1 添加 maven 依赖

    <parent><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-parent</artifactId><version>2.2.6.RELEASE</version><relativePath/></parent><properties><maven.compiler.source>8</maven.compiler.source><maven.compiler.target>8</maven.compiler.target></properties><dependencies><dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-test</artifactId><scope>test</scope></dependency><dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-data-mongodb</artifactId></dependency></dependencies>

3.4.2 创建 application.yml 

spring:# 数据源配置data:mongodb:# 主机地址host: 192.168.229.154# 数据库database: articledb# 默认端口是27017port: 27017# 也可以使用uri连接#uri: mongodb://192.168.40.134:27017/articledb

3.4.3 创建启动类

package cn.article;import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;@SpringBootApplication
public class ArticleApplication {public static void main(String[] args) {SpringApplication.run(ArticleApplication.class, args);}
}

        启动项目,看是否能正常启动,控制台没有错误。

3.4.4 创建评论实体类

        在 cn.article.po 包下创建创建实体类 Comment ,如下:

package cn.article.po;import org.springframework.data.annotation.Id;
import org.springframework.data.mongodb.core.index.Indexed;
import org.springframework.data.mongodb.core.mapping.Document;
import org.springframework.data.mongodb.core.mapping.Field;import java.time.LocalDateTime;
import java.util.Date;/*** @Document(collection="comment")*    把一个java类声明为mongodb的文档,可以通过collection参数指定这个类对应的文档。*    @Document(collection="mongodb 对应 collection 名")*    若未加 @Document ,该 bean save 到 mongo 的 comment collection*    若添加 @Document ,则 save 到 comment collection*    可以省略,如果省略,则默认使用类名小写映射集合*/
@Document(collection="comment")
// 如果想要添加复合索引,则在类的上面使用 @CompoundIndex 标签,例如:@CompoundIndex( def = "{'userid': 1, 'nickname': -1}")
public class Comment {// @Id为主键标识,该属性的值会自动对应 mongodb 的主键字段 "_id",如果该属性名就叫 “id”,则该注解可以省略,否则必须写@Id// 主键private String id;// @Field 标签的意思是对应 mongodb 的字段的名字,如果一致,则无需该注解@Field("content")// 吐槽内容private String content;// 发布日期private Date publishtime;// 添加了一个单字段的索引@Indexed// 发布人IDprivate String userid;// 昵称private String nickname;// 评论的日期时间private LocalDateTime createdatetime;// 点赞数private Integer likenum;// 回复数private Integer replynum;// 状态private String state;// 上级IDprivate String parentid;// 标题IDprivate String articleid;// setter、getter、toString()
}

说明: 

        索引可以大大提升查询效率,一般在查询字段上添加索引,索引的添加可以通过 Mongo 的命令来添加,也可以在 Java 的实体类中通过注解添加。

1):单字段索引注解 @Indexed

        声明该字段需要索引,建索引可以大大的提高查询效率。

# 使用 mongoDB 命令创建单字段索引
db.comment.createIndex({"userid":1})

2):复合索引注解 @CompoundIndex

        复合索引的声明,建复合索引可以有效地提高多字段的查询效率。

# 使用 mongoDB 命令创建复合索引
db.comment.createIndex({"userid":1,"nickname":-1})

3.4.5 创建评论服务层

        在 cn.article.dao 包下创建接口 CommentRepository,如下:

package cn.article.dao;import cn.article.po.Comment;
import org.springframework.data.mongodb.repository.MongoRepository;// 评论的持久层接口
public interface CommentRepository  extends MongoRepository<Comment,String> {}

        在 cn.article.service 包下创建类 CommentService,如下:

package cn.article.service;import cn.article.dao.CommentRepository;
import cn.article.po.Comment;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;import java.util.List;// 评论的业务层
@Service
public class CommentService {@Autowiredprivate CommentRepository commentRepository;// 保存评论public void saveComment(Comment comment) {// 如果需要自定义主键,可以在这里指定主键;如果不指定主键,MongoDB 会自动生成主键commentRepository.save(comment);}// 更新评论public void updateComment(Comment comment) {commentRepository.save(comment);}// 根据id删除评论public void deleteCommentById(String id) {commentRepository.deleteById(id);}// 查询所有评论public List<Comment> findCommentList() {return commentRepository.findAll();}// 根据id查询评论public Comment findCommentById(String id) {return commentRepository.findById(id).get();}
}

        新建 Junit 测试类 CommentServiceTest ,测试保存和查询所有,代码如下:

package cn.article.service;import cn.article.ArticleApplication;
import cn.article.po.Comment;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.test.context.junit4.SpringRunner;
import java.time.LocalDateTime;
import java.util.List;// 测试评论的业务层
@RunWith(SpringRunner.class)
@SpringBootTest(classes = ArticleApplication.class)
public class CommentServiceTest {@Autowiredprivate CommentService commentService;// 保存一条评论@Testpublic void testSaveComment(){Comment comment=new Comment();comment.setArticleid("100000");comment.setContent("测试添加的数据");comment.setCreatedatetime(LocalDateTime.now());comment.setUserid("1003");comment.setNickname("凯撒大帝");comment.setState("1");comment.setLikenum(0);comment.setReplynum(0);commentService.saveComment(comment);}// 查询所有数据@Testpublic void testFindAll(){List<Comment> list = commentService.findCommentList();System.out.println(list);}// 测试根据id查询@Testpublic void testFindCommentById(){Comment comment = commentService.findCommentById("1");System.out.println(comment);}
}

3.5 实现分页效果

        接下来我们实现一个根据上级 ID 查询文章评论的分页列表的功能,首先在 CommentRepository 类中新增方法定义,如下:

// 根据父 id,查询子评论的分页列表
// 需要注意的是这个方法名是有规定的,必须是 findBy属性名,不是随便起的
Page<Comment> findByParentid (String parentid, Pageable pageable);

        在 CommentService 类中新增方法,如下:

// 根据父id查询分页列表
public Page<Comment> findCommentListPageByParentid(String parentid, int page , int size){return commentRepository.findByParentid(parentid, PageRequest.of(page-1,size));
}

        在 CommentServiceTest 测试类中添加测试代码,如下:

// 测试根据父id查询子评论的分页列表
@Test
public void testFindCommentListPageByParentid(){Page<Comment> pageResponse = commentService.findCommentListPageByParentid("3", 1, 2);System.out.println("----总记录数:"+pageResponse.getTotalElements());System.out.println("----当前页数据:"+pageResponse.getContent());
}

        使用 compass 工具将 comment 里面的数据添加一个 parentid 属性,方便我们进行测试,添加结束后,执行执行方法,结果如下:

3.6 实现评论的点赞

        我们先看一下以下点赞的临时示例代码: CommentService 新增 updateThumbup 方法,如下:

/*** 点赞-效率低* @param id*/
public void updateCommentThumbupToIncrementingOld(String id){Comment comment = CommentRepository.findById(id).get();comment.setLikenum(comment.getLikenum()+1);CommentRepository.save(comment);
}

        以上方法虽然实现起来比较简单,但是执行效率并不高,因为我只需要将点赞数加 1 就可以了,没必要查询出所有字段修改后再更新所有字段。

        我们可以使用 MongoTemplate 类来实现对某列的操作。修改 CommentService 类如下所示:

// 注入MongoTemplate
@Autowired
private MongoTemplate mongoTemplate;/*** 点赞数+1* @param id*/
public void updateCommentLikenum(String id){// 查询对象Query query=Query.query(Criteria.where("_id").is(id));// 更新对象Update update=new Update();// 局部更新,相当于$set// update.set(key,value)// 递增$inc// update.inc("likenum",1);update.inc("likenum");//参数1:查询对象;参数2:更新对象;参数3:集合的名字或实体类的类型Comment.classmongoTemplate.updateFirst(query,update,"comment");
}

        在 CommentServiceTest 测试类中添加测试代码,如下:

/*** 点赞数+1*/
@Test
public void testUpdateCommentLikenum(){//对3号文档的点赞数+1commentService.updateCommentLikenum("4");
}

        执行测试用例即可。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.rhkb.cn/news/156774.html

如若内容造成侵权/违法违规/事实不符,请联系长河编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

Android Studio新建项目教程

Android Studio新建项目教程 一、创建新项目 二、选择空白页项目类型 配置然后finish 等待项目完成初试化 等待初始化结束&#xff0c;创建完成 三、运行创建的APP

京东历史价格数据接口,京东商品历史价格接口,京东API接口

京东商品历史价格数据接口采集方法如下&#xff1a; 注册京东开发者账号&#xff0c;并创建应用&#xff0c;获取到应用ID&#xff08;appID&#xff09;、应用密钥&#xff08;appSecret&#xff09;以及访问令牌&#xff08;accessToken&#xff09;。获取接口请求地址。根据…

华为数通方向HCIP-DataCom H12-831题库(单选题:241-260)

第241题 某园区部署了IPV6进行业务测试,该网络中有4台路由器(R1R2、R3和R),运行OSPFV3实现Pv6网络的互联互通。有一台新的路由器R5需要接入网络进行测试,某工程师通过在R4的OSPFV3进程中引入直连路由,实现园区网内的设备能够访问R5的GEO/0/1口地址。关千该场景的描述,错误…

云计算到底牛x在哪里?

你们好&#xff0c;我的网工朋友。 云计算已经霸屏行业有段时间了&#xff0c;但很多粉丝朋友还是不太明白什么是云计算&#xff0c;为什么要学云计算。 从宏观来说&#xff0c;其实云计算的优点很多。 就和传统模式相比&#xff0c;云计算在六个维度都有显著的提升点。 比…

Vue-2.7自定义指令

自定义指令 自己定义的指令&#xff0c;可以封装一些dom操作&#xff0c;扩展额外功能 例如需求&#xff1a;当页面加载时&#xff0c;让元素将获得焦点&#xff08;autofucus在safari浏览器有兼容性&#xff09; 操作dom&#xff1a;dom元素.focus() 太麻烦&#xff01;…

Zend Framework 3.1.3 gadget chain

前言 在推特上的PT SWARM账号发布了一条消息。 一个名为Zend Framework的php框架出现了新的gadget chain&#xff0c;可导致RCE。笔者尝试复现&#xff0c;但失败了。所幸&#xff0c;我基于此链&#xff0c;发现在这个框架的最新版本中的另一条链。 复现过程 这里使用vscod…

ArcGIS/GeoScene脚本:基于粒子群优化的支持向量机回归模型

参数输入 1.样本数据必须包含需要回归的字段 2.回归字段是数值类型 3.影响因子是栅格数据&#xff0c;可添加多个 4.随机种子可以确保每次运行的训练集和测试集一致 5.训练集占比为0-1之间的小数 6.迭代次数&#xff1a;迭代次数越高精度越高&#xff0c;但是运行时间越长…

机器学习 - 似然函数:概念、应用与代码实例

目录 一、概要二、什么是似然函数数学定义似然与概率的区别重要性举例 三、似然函数与概率密度函数似然函数&#xff08;Likelihood Function&#xff09;定义例子 概率密度函数&#xff08;Probability Density Function, PDF&#xff09;定义 区别与联系 四、最大似然估计&am…

网络流量安全分析-工作组异常

在网络中&#xff0c;工作组异常分析具有重要意义。以下是网络中工作组异常分析的几个关键点&#xff1a; 检测网络攻击&#xff1a;网络中的工作组异常可能是由恶意活动引起的&#xff0c;如网络攻击、病毒感染、黑客入侵等。通过对工作组异常的监控和分析&#xff0c;可以快…

8年经验之谈 —— 如何用 JMeter 编写性能测试脚本?

Apache JMeter 应该是应用最广泛的性能测试工具。怎么用 JMeter 编写性能测试脚本&#xff1f; 1. 编写 HTTP 性能测试脚本 STEP 1. 添加 HTTP 请求 i STEP 2. 了解配置信息 HTTP 请求各项信息说明&#xff08;以 JMeter 5.1 为例&#xff09;。 如下图所示&#xff1a; W…

单目标应用:墨西哥蝾螈优化算法(Mexican Axolotl Optimization,MAO)求解微电网优化--MATLAB代码

一、微网系统运行优化模型 微电网优化模型介绍&#xff1a; 微电网多目标优化调度模型简介_IT猿手的博客-CSDN博客 二、墨西哥蝾螈优化算法MAO 墨西哥蝾螈优化算法&#xff08;Mexican Axolotl Optimization&#xff0c;MAO&#xff09;由Yenny Villuendas-Rey 1等人于2021…

javaweb:mybatis:mapper(sql映射+代理开发+配置文件之设置别名、多环境配置、顺序+注解开发)

1.0版本 sql映射文件实现 流程 首先程序进入启动类MyBatisDemo.java中&#xff0c;读取配置文件mybatis-config.xml 再由mybatis-config的mappers属性 <mappers><mapper resource"UserMapper.xml"></mapper></mappers>找到sql映射文件Use…

HarmonyOS学习 -- ArkTS开发语言入门

文章目录 一、编程语言介绍二、TypeScript基础类型1. 布尔值2. 数字3. 字符串4. 数组5. 元组6. 枚举7. unknown8. void9. null 和 undefined10. 联合类型 三、TypeScript基础知识条件语句if语句switch语句 函数定义有名函数和匿名函数可选参数剩余参数箭头函数 类1. 类的定义2.…

uniapp 微信小程序 vue3.0+TS手写自定义封装步骤条(setup)

uniapp手写自定义步骤条&#xff08;setup&#xff09; 话不多说 先上效果图&#xff1a; setup.vue组件代码&#xff1a; <template><view class"stepBox"><viewclass"stepitem"v-for"(item, index) in stepList":key"i…

Matlab之查询子字符串在字符串中的起始位置函数strfind

一、功能 strfind函数用于在一个字符串中查找指定的子字符串&#xff0c;并返回子字符串在字符串中的起始位置。 二、语法 indices strfind(str, pattern) 其中&#xff0c;str是要进行查找的字符串&#xff0c;pattern是要查找的子字符串。 函数会返回一个由子字符串在字…

区分Cookie,Session,Token

Cookie 由于HTTP 协议是一个无状态协议&#xff0c;客户端向服务器发请求&#xff0c;服务器返回响应。并且你每次都要输入账号和密码进行登录&#xff0c;对于用户来说非常的麻烦&#xff01;这种背景下&#xff0c;就产生了 Cookie cookie 存储在客户端&#xff1a; cookie…

软件工程与计算总结(八)软件设计基础

一.设计思想的发展 1958&#xff1a;软件这个名词第一次在公开刊物上使用~60年代中后期and70年代前中期&#xff1a;结构化编程、逐步求精、自顶向下理念是程序设计主要方法70年代中后期and90年代&#xff1a;结构化设计方法、抽象数据类型、信息隐藏、封装、继承、多态等思想…

sshpass传输文件提示Host key verification failed.

1. sshpass功能简述 sshpass指令可用于A服务器向B服务器传输文件或执行某些指令。 2. 传输文件指令 基本传输命令&#xff1a;sshpass -p 远程服务器登录密码 scp 本地路径文件 远程服务器登录用户名远程服务器IP地址:远程服务器文件保存路径 示例&#xff1a; sshpass -p 1…

【Java 进阶篇】JavaScript `typeof` 操作符详解

JavaScript是一种弱类型语言&#xff0c;这意味着变量的数据类型通常是灵活的。为了更好地理解和操作数据&#xff0c;JavaScript提供了typeof操作符&#xff0c;它可以用来确定一个值的数据类型。在本篇博客中&#xff0c;我们将详细讨论typeof操作符&#xff0c;包括它的用法…

系统架构师最新版教材 - 计算机系统知识01

说明 本篇博客主要围绕2022年系统架构师最新版教程&#xff0c;算上时间&#xff0c;今年应该是这一版教材的第一次考试&#xff0c;说来也气人&#xff0c;一年考一次&#xff0c;然后我毅然就直接报名了&#xff0c;报名之前还不知道教程已经改版了&#xff0c;到近期刷题的…