MongoDB基础操作
1.MongoDB 简介
的非关系型数据库.
它支持的数据结构非常
**松散**
, 是一种类似于 JSON 的 格式叫BSON
, 所以它既可以存储比较复杂的数据类型, 又相当的灵活.**MongoDB中的记录是一个文档, 它是一个由字段和值对(field:value)组成的数据结构.MongoDB文档类似于JSON对象, 即一个文档认为就是一个对象.**字段的数据类型是字符型, 它的值除了使用基本的一些类型外, 还可以包括其他文档, 普通数组和文档数组.
2.MongoDB的应用场景
==传统的关系型数据库 (比如 MySQL), 在数据操作的”三高”需求以及对应的 Web 2.0 网站需求面前, 会有”力不从心”的感觉。==所谓的三高需求:
高并发, 高性能, 高可用, 简称三高
High Performance
: 对数据库的高并发读写的要求High Storage:
对海量数据的高效率存储和访问的需求High Scalability && High Available:
对数据的高扩展性和高可用性的需求
而 MongoDB 可以应对三高需求
具体的应用场景:
- 社交场景:使用 MongoDB 存储存储用户信息, 以及用户发表的朋友圈信息, 通过地理位置索引实现附近的人, 地点等功能.
- 游戏场景:使用 MongoDB 存储游戏用户信息, 用 户的装备, 积分等直接以内嵌文档的形式存储, 方便查询, 高效率存储和访问.
- 物流场景:使用 MongoDB 存储订单信息, 订单状态在运送过程中会不断更新, 以 MongoDB 内嵌数组的形式来存储, 一次查询就能将订单所有的变更读取出来.
- 物联网场景:使用 MongoDB 存储所有接入的智能设备信息, 以及设备汇报的日志信息, 并对这些信息进行多维度的分析.
- 视频直播:使用 MongoDB 存储用户信息, 点赞互动信息等.
这些应用场景中, 数据操作方面的共同点有:
- 数据量大
- 写入操作频繁
- 价值较低的数据, 对事务性要求不高
对于这样的数据, 更适合用 MongoDB 来实现数据存储
那么我们什么时候选择 MongoDB 呢?
除了架构选型上, 除了上述三个特点之外, 还要考虑下面这些问题:
- 应用不需要事务及复杂 JOIN 支持
- 新应用, 需求会变, 数据模型无法确定, 想快速迭代开发
- 应用需要 2000 - 3000 以上的读写QPS(更高也可以)
- 应用需要 TB 甚至 PB 级别数据存储
- 应用发展迅速, 需要能快速水平扩展
- 应用要求存储的数据不丢失
- 应用需要
99.999%
高可用- 应用需要大量的地理位置查询, 文本查询
如果上述有1个符合, 可以考虑 MongoDB, 2个及以上的符合, 选择 MongoDB 绝不会后悔.
如果用MySQL呢?
相对MySQL, 可以以更低的成本解决问题(包括学习, 开发, 运维等成本)
3.MongoDB 的特点
1. 高性能
MongoDB 提供高性能的数据持久化
- 嵌入式数据模型的支持减少了数据库系统上的 I/O 活动
- 索引支持更快的查询, 并且可以包含来自嵌入式文档和数组的键 (文本索引解决搜索的需求, TTL 索引解决历史数据自动过期的需求, 地理位置索引可以用于构件各种 O2O 应用)
- mmapv1, wiredtiger, mongorocks (rocksdb) in-memory 等多引擎支持满足各种场景需求
- Gridfs 解决文件存储需求
2. 高可用
MongoDB 的复制工具称作副本集 (replica set) 可以提供自动故障转移和数据冗余
3. 高扩展
水平扩展是其核心功能一部分,分片将数据分布在一组集群的机器上 (海量数据存储, 服务能力水平扩展)
MongoDB 支持基于片键创建数据区域, 在一个平衡的集群当中, MongoDB 将一个区域所覆盖的读写只定向到该区域的那些片
4. 丰富的查询支持
==MongoDB支持丰富的查询语言, 支持读和写操作(CRUD), 比如数据聚合, 文本搜索和地理空间查询等.==
4.Docker安装MongoDB
注意:MongoDB 默认直接连接,无须身份验证,如果当前机器可以公网访问,且不注意Mongodb 端口(默认 27017)的开放状态,那么Mongodb就会产生安全风险
- 拉取镜像
docker pull mongo:latest
- 创建mongo数据持久化目录
mkdir -p /mydata/mongodb/data
- 运行容器
docker run -it -d --name mongo -v /mydata/mongodb/data:/data/db -p 27017:27017 mongo:latest --auth
- -v: 将宿主机的/docker_volume/mongodb/data映射到容器的/data/db目录,将数据持久化到宿主机,以防止删除容器后,容器内的数据丢失
- –auth:需要密码才能访问容器服务
- 登录mongo容器,并进入到【admin】数据库
docker exec -it mongo mongo admin
# MongoDB 6.0 及以上版本使用以下命令
docker exec -it mongo mongosh admin
- 创建一个用户,mongo 默认没有用户
db.createUser({ user:'root',pwd:'123456',roles:[ { role:'userAdminAnyDatabase', db: 'admin'},'readWriteAnyDatabase']});
【user:‘root’ 】:设置用户名为root
【pwd:‘123456’】:设置密码为123456
【role:‘userAdminAnyDatabase’】:只在admin数据库中可用,赋予用户所有数据库的userAdmin权限
【db: ‘admin’】:可操作的数据库
【‘readWriteAnyDatabase’】:赋予用户读写权限
- 使用命令连接数据库
db.auth('root', '123456')
- 测试插入数据
db.user.insert({"name":"zhangsan","age":18})
- 查询刚才插入的语句
db.user.find()
- navicat连接测试
5.MongoDB的核心概念
库
mongodb中的库就类似于传统关系型数据库中库的概念,==用来通过不同库隔离不同应用数据==。mongodb中可以建立多个数据库。每一个库都有自己的集合和权限,不同的数据库也放置在不同的文件中。默认的数据库为”test”,数据库存储在启动指定的data目录中。
集合
集合就是 MongoDB 文档组,类似于 RDBMS (关系数据库管理系统:Relational Database Management System)中的表的概念。集合存在于数据库中,一个库中可以创建多个集合。每个集合没有固定的结构,这意味着你在对集合可以插入不同格式和类型的数据,但通常情况下我们插入集合的数据都会有一定的关联性。
文档
文档集合中一条条记录,是一组键值(key-value)对(即 BSON)。**MongoDB 的文档不需要设置相同的字段,并且相同的字段不需要相同的数据类型**,这与关系型数据库有很大的区别,也是 MongoDB 非常突出的特点。
一个简单的文档例子如下:d
{"site":"www.baizhiedu.xin", "name":"编程不良人"}
MongoDB和RDBMS的关系总结:
6.mongoDB存储原理
- mongoDb采用内存加磁盘的方式存储数据;
- mongoDb支持数据分片,当单一的服务器中磁盘不够用的时候,还可以串联其他服务器;
- 客户端的请求到达内存时,先在日志中记录下操作记录,然后再去操作内存;
- 内存中的日志每10ms向磁盘中的日志进行同步一次,数据则每分钟同步一次;
- 客户端先去内存中查询数据,内存中没有再去查询磁盘;
- 当客户端写入的时候,会先写入到内存中,内存中写入后请求直接返回,内存中的数据会根据同步策略同步到磁盘;
- 如果机器宕机,在重启服务的时候会解析磁盘中的日志和磁盘中的数据进行对比,将未入到磁盘中的数据写入磁盘,但可能会丢失10ms的数据;
7.数据库操作
mongodb默认保留的数据库
- admin: 从权限角度考虑, 这是
root
数据库, 如果将一个用户添加到这个数据库, 这个用户自动继承所有数据库的权限, 一些特定的服务器端命令也只能从这个数据库运行, 比如列出所有的数据库或者关闭服务器 - local: 数据永远不会被复制, 可以用来存储限于本地的单台服务器的集合 (部署集群, 分片等)
- config: Mongo 用于分片设置时,
config
数据库在内部使用, 用来保存分片的相关信息
2.1创建并选择数据库
- 创建并选择数据库的语法格式:
use 数据库名称
如果数据库不存在则自动创建,例如,以下语句创建 articledb数据库:
use articledb
注意: 在 MongoDB 中,集合只有在内容插入后才会显示!
2.2查看数据库
- 查看所有的数据库
show dbs
或
show databases
- 查看当前正在使用的数据库命令
db
2.3数据库的删除
- MongoDB 删除数据库的语法格式如下:
db.dropDatabase()
或
db.数据库名.drop()
8.集合操作
8.1查看集合
查看集合的语法如下:
# 查看集合
show collections
8.2创建集合
创建集合的语法如下:
db.createCollection('集合名称', [options])
options可以是如下参数:
字段 | 类型 | 描述 |
---|---|---|
capped | 布尔 | (可选)如果为 true,则创建固定集合。固定集合是指有着固定大小的集合,当达到最大值时,它会自动覆盖最早的文档。 当该值为 true 时,必须指定 size 参数。 |
size | 数值 | (可选)为固定集合指定一个最大值,即字节数。 如果 capped 为 true,也需要指定该字段。 |
max | 数值 | (可选)指定固定集合中包含文档的最大数量。 |
8.3删除集合
删除集合的语法如下:
db.集合名称.drop()
9.文档的操作
9.1 文档的插入
- 单个文档的插入,语法如下:
db.collection.insert/insertOne(
<document or array of documents>,
{
writeConcern: <document>,
ordered: <boolean>
}
)
注意点:
1)**comment集合如果不存在,则会隐式创建**
2)**mongo中的数字,默认情况下是double类型,如果要存整型,必须使用函数NumberInt(整型数字)。**
3)插入当前日期使用 new Date()
4)插入的数据没有指定 _id ,会自动生成主键值
5)如果某字段没值,可以赋值为null,或不写该字段。
插入一条数据如下:
/*插入一条数据*/
db.articledb.insert({"name":"zhangsan","age":NumberInt(22)});
db.articledb.insertOne({"name":"lisi","age":NumberInt(20)});
- 文档的批量插入,语法如下:
db.集合名称.insertMany(
[ <document 1> , <document 2>, ... ],
{
writeConcern: 1,//写入策略,默认为1,即要求确认写操作,0是不要求。
ordered: true //指定是否按顺序写入,默认true,按顺序写入。
}
)
/*插入多条数据*/
db.articledb.insertMany([{"name":"wangwu","age":NumberInt(21)},{"name":"zhaoliu","age":NumberInt(23)}])
- 内嵌文档的插入
db.user.insert({"name":"wangnima","age":NumberInt(15),"home":{"province":"河南省","county":"扶沟县"}})
- 数组的插入
db.user.insert({"name":"baqi","age":NumberInt(16),"score":[{"chinese":NumberInt(99)},{"math":NumberInt(109)},{"english":NumberInt(425)}]})
4.2文档的查询
文档查询的语法格式如下:
db.集合名称.find(<query>, [projection])
query
可选,使用查询操作符指定查询条件projection
可选,使用投影操作符指定返回的键。查询时返回文档中所有键值,只需省略该参数即可(默认省略)。投影时,id为1的时候,其他字段必须是1;id是0的时候,其他字段可以是0;如果没有_id字段约束,多个其他字段必须同为0或同为1。
如果需要以容读的方式来读取数据,可以使用pretty()
方法:
db.集合名称.find(<query>, [projection]).pretty();
- 查询集合的所有文档
db.collection.find()
或
db.collection.find({})
==发现每条文档会有一个叫_id的字段,这个相当于我们原来关系数据库中表的主键,当你在插入文档记录时没有指定该字段, MongoDB会自动创建,其类型是ObjectID类型。 如果我们在插入文档记录时指定该字段也可以,其类型可以是ObjectID类型,也可以是MongoDB支持的任意类型。==
- 添加参数查询文档
查询出所有匹配参数的文档
db.articledb.find({"age":"22"})
使用findOne
查询出匹配的第一条数据
db.articledb.findOne({"age":NumberInt(22)})
- 查询嵌套文档
db.user.find({"home.province":"江苏省"})
db.user.find({"home":{"province":"江苏省","county":"昆山市","town":"陆家镇"}})
db.user.find({“home”:{“province”:”江苏省”,”county”:”昆山市”,”town”:”陆家镇”}})
- 查询数组
查询兴趣是“reading”,“movie”,“swimming” 的用户的userid及nikename
db.comment.find({hobby:["reading","movie","swimming"]},{userid:1,nickname:1})
查询兴趣中有“movie”这一项的用户的userid及nikename:
db.comment.find({hobby:"movie"},{userid:1,nickname:1})
查询喜欢“reading”和“movie”的用户的userid及nikename:
db.comment.find({hobby:{$all:["reading","movie"]}},{userid:1,nickname:1})
查询喜欢“reading”或“movie”的用户的userid及nikename:
db.comment.find({hobby:{$in:["reading","movie"]}},{userid:1,nickname:1}) .
查询兴趣爱好有三个的用户的userid及nikename:
db.comment.find({hobby:{$size:3}},{userid:1,nickname:1})
4.3文档的更新
语法格式:
db.集合名称.update(
<query>,
<update>,
{
upsert: <boolean>,
multi: <boolean>,
writeConcern: <document>
}
);
- query:描述更新的查询条件
- update:描述更新的动作及新的内容;
- options:描述更新的选项
- upsert:可选,如果不存在update的记录,是否插入新的记录。默认false,不插入
- multi:可选,是否按条件查询出的多条记录全部更新。默认false,只更新找到的第1
条记录- writeConcern :可选,决定一个写操作落到多少个节点上才算成功。
- 覆盖修改
当我们未指定其他要修改的字段时,修改的话就会覆盖其他字段的值,其他字段的值为null
- 局部修改
要使用修改器$set
来实现局部修改
db.articledb.update({"name":"zhangsan"},{$set:{"age":NumberInt(22)}})
3. 批量修改
mongodb
默认只会修改第一条数据,如果要根据字段批量删除字段,需要设置multi
为true
db.articledb.update({"age":22},{$set:{"name":"zhangquandan"}},{"multi":true})
- 新增文档字段
db.user.update({"name":"zhangsan"},{$set:{"address":"苏州"}})
- 删除文档字段
db.user.update({"name":"zhangsan"},{$unset:{"address":"苏州"}})
- 修改嵌套文档
修改嵌套数据:
db.user.update({"name":"wangnima"},{$set:{"home.province":"江苏省"}})
db.user.update({"name":"wangnima"},{$set:{"home":{"province":"江苏省","county":"昆山市"}}})
向内嵌文档中添加数据:
db.user.update({"name":"wangnima"},{$set:{"home.town":"陆家镇"}})
- 修改数组
mongodb中数组的下标也是从0
开始的
数组的修改
db.user.update({"name":"qianba"},{$set:{"likes.1":"打豆豆"}})
数组中内嵌文档的修改
db.user.update({"name":"baqi"},{$set:{"score.0.chinese":NumberInt(119)}})
4.4文档的删除
删除文档的语法结构:
db.集合名称.remove(条件)
以下语句可以将数据全部删除,请慎用
db.comment.remove({})
- remove 命令需要配合查询条件使用;
- 匹配查询条件的文档会被删除;
- 指定一个空文档条件会删除所有文档;
db.articledb.remove({"name":"zhaoliu"})
4.5文档的统计查询
统计查询使用count()方法,语法如下:
db.collection.count(query, options)
- 统计所有记录数
db.articledb.count()
- 按条件统计记录数
db.articledb.count({"age":22})
4.6文档的分页查询
可以使用limit()
方法来读取指定数量的数据,使用skip()
方法来跳过指定数量的数据。
基本语法如下所示:
db.COLLECTION_NAME.find().limit(NUMBER).skip(NUMBER)
如果你想返回指定条数的记录,可以在find方法后调用limit来返回结果(TopN),默认值20,例如:
返回前两条数据
db.articledb.find().limit(2)
skip方法同样接受一个数字参数作为跳过的记录条数。(前N个不要),默认值是0,例如:
查询第3-4条数据
db.articledb.find().limit(2).skip(2)
4.7文档的排序查询
sort()
方法对数据进行排序,sort()
方法可以通过参数指定排序的字段,并使用 1
和-1
来指定排序的方式,**其中 1 为升序排列,而 -1 是用 于降序排列**。
语法如下所示:
db.COLLECTION_NAME.find().sort({KEY:1})
或
db.集合名称.find().sort(排序方式)
例如:按照年龄的升序进行排序
db.articledb.find().sort({"age":1})
4.8文档的模糊查询
MongoDB的模糊查询是通过正则表达式的方式实现的。正则表达式是js的语法,直接量的写法。格式为:
db.collection.find({field:/正则表达式/})
或
db.集合.find({字段:/正则表达式/})
# 查询以正则表达式开头的文档
db.集合.find({字段:/^正则表达式/})
# 查询以正则表达式结尾的文档
db.集合.find({字段:/正则表达式$/})
例如:查询name
中含有quan
的文档
db.articledb.find({"name":/quan/})
查询以li
开头的文档
db.articledb.find({"name":/^li/})
4.9文档的比较查询
<, <=, >, >= 这个操作符是文档的比较查询,格式如下:
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 }}) // 不等于: field != value
例如:查询年龄大于21的文档
db.articledb.find({"age":{$gt:NumberInt(21)}})
4.10文档的包含查询
包含使用$in
操作符。
不包含使用$nin
操作符。
例如:查询年龄在21-22之间的文档
db.articledb.find({"age":{$in:[21,22]}})
查询年龄不在21-22之间的文档
db.articledb.find({"age":{$nin:[21,22]}})
4.11文档的去重查询
去重的操作语法如下:
db.集合名称.distinct('字段')
如:按照年龄去重
db.user.distinct("age")
4.12文档的投影查询
如果要查询结果返回部分字段,则需要使用投影查询(不显示所有字段,只显示指定的字段)。而_id 会默认显示。
其中1
表示显示,而0
表示排除
db.articledb.find({"name":"zhangsan"},{"name":1})
如果不想显示_id
,可以指定_id
为0
db.articledb.find({"name":"zhangsan"},{"name":1,"_id":0})
4.13条件连接查询
and
操作符
我们如果需要查询同时满足两个以上条件,需要使用$and操作符将条件进行关联。(相 当于SQL的and) 格式为:
$and:[ { },{ },{ } ]
示例:查询年龄大于20,并且年龄小于等于21的文档
db.articledb.find({$and:[{"age":{$gt:NumberInt(20)}},{"age":{$lte:NumberInt(21)}}]})
or
操作符
如果两个以上条件之间是或者的关系,我们使用$or
操作符进行关联,与前面 and的使用方式相同 格式为:
$or:[ { },{ },{ } ]
db.articledb.find({$or:[{"age":{$lt:NumberInt(21)}},{"name":"zhangquandan"}]})
and
操作符和**or
操作符**联合查询
例如关系型数据库的查询语句:
SELECT ... FROM user WHERE age >= 18 AND (name = "zhangsan" OR name = "lisi")
db.user.find({"age":{$gt:18},$or:[{"name":"zhangsan"},{"name":"lisi"}]})
4.14文档数组的查询
新增一个数组数据如下:
db.user.insert({"name":"qianba","age":12,"likes":["看电影","骑行"]})
- 按照数组内容查询
查询喜欢看电影
的文档信息:
db.user.find({"likes":"看电影"})
//将张力的书架上的第二本书修改为“C#”
db.students.updateOne({"name": "张力"}, {$set: {"books.1": "C#"}})
- 按照数组长度查询
db.user.find({"likes":{$size:2}})
10.$type操作符
$type操作符是基于BSON类型来检索集合中匹配的数据类型,并返回结果。
MongoDB 中可以使用的类型如下表所示:
类型 | 数字 |
---|---|
Double |
1 |
String |
2 |
Object |
3 |
Array |
4 |
Binary data |
5 |
Undefined(已废除) |
6 |
Object id |
7 |
Boolean |
8 |
Date |
9 |
Null |
10 |
Regular Expression |
11 |
JavaScript |
13 |
Symbol |
14 |
JavaScript (with scope) |
15 |
32-bit integer |
16 |
Timestamp |
17 |
64-bit integer |
18 |
Min key |
255 |
Max key |
127 |
如查询age
的类型为double
类型的文档
db.user.find({"age":{$type:1}})
11.索引
11.1索引的概述
索引支持在MongoDB中高效地执行查询。如果没有索引,MongoDB必须执行全集合扫描,即扫描集合中的每个文档,以选择与查询语句 匹配的文档。这种扫描全集合的查询效率是非常低的,特别在处理大量的数据时,查询可以要花费几十秒甚至几分钟,这对网站的性能是非常致命的。
如果查询存在适当的索引,MongoDB可以使用该索引限制必须检查的文档数。
索引是特殊的数据结构,它以易于遍历的形式存储集合数据集的一小部分。索引存储特定字段或一组字段的值,按字段值排序。索引项的排序支持有效的相等匹配和基于范围的查询操作。此外,MongoDB还可以使用索引中的排序返回排序结果。
官网文档:https://docs.mongodb.com/manual/indexes/
了解: MongoDB索引使用B树数据结构(确切的说是B-Tree,MySQL是B+Tree)
11.2索引的分类
11.2.1单字段索引
MongoDB支持在文档的单个字段上创建用户定义的升序/降序索引,称为单字段索引(Single Field Index)。 对于单个字段索引和排序操作,索引键的排序顺序(即升序或降序)并不重要,因为MongoDB可以在任何方向上遍历索引。
11.2.2 复合索引
**MongoDB还支持多个字段的用户定义索引,即复合索引(Compound Index)**。 复合索引中列出的字段顺序具有重要意义。例如,如果复合索引由 { userid: 1, score: -1 } 组成,则索引首先按userid正序排序,然后 在每个userid的值内,再在按score倒序排序。
11.2.3 其他索引
- 地理空间索引(Geospatial Index)
为了支持对地理空间坐标数据的有效查询,MongoDB提供了两种特殊的索引:
返回结果时使用平面几何的二维索引和返回结果时使用球面 几何的二维球面索引。
- 文本索引(Text Indexes)
MongoDB提供了一种文本索引类型,支持在集合中搜索字符串内容。这些文本索引不存储特定于语言的停止词(例如“the”、“a”、“or”), 而将集合中的词作为词干,只存储根词。
- 哈希索引(Hashed Indexes)
为了支持基于散列的分片,MongoDB提供了散列索引类型,它对字段值的散列进行索引。这些索引在其范围内的值分布更加随机,但只支 持相等匹配,不支持基于范围的查询。
11.3索引的创建
创建索引,语法如下:
db.集合名称.createIndex(keys, options)
db.集合名称.createIndex({"title":1,"description":-1})
说明: 语法中 Key 值为你要创建的索引字段,1 为指定按升序创建索引
,如果你想按降序来创建索引指定为 -1 即可。
options的列表
创建单字段索引
其中
-1
表示降序
db.articledb.createIndex({"age":-1})
- 创建复合索引
db.articledb.createIndex({"age":1,"name":-1})
11.4索引的查看
查看当前集合的所有索引:
db.集合名称.getIndexes()
mongodb
会默认根据主键创建一个主键
MongoDB在创建集合的过程中,在 _id 字段上创建一个唯一的索引,默认名字为 id ,该索引可防止客户端插入两个具有相同值的文 档,您不能在_id字段上删除此索引。 注意:该索引是唯一索引,因此值不能重复,即 _id 值不能重复的。在分片集群中,通常使用 _id 作为片键。
11.5索引的删除
可以移除指定的索引,或移除所有索引
- 移除指定的索引
语法:
db.集合名称.dropIndex(index)
例如:删除age
字段上的降序
索引
db.articledb.dropIndex({"age":-1})
- 删除所有索引
db.集合名称.dropIndexes()
11.6性能分析工具explain
- 执行计划
分析查询性能(Analyze Query Performance)通常使用执行计划(解释计划、Explain Plan)来查看查询的情况,如查询耗费的时间、是 否基于索引查询等。
通常,我们想知道,建立的索引是否有效,效果如何,都需要通过执行计划查看。
语法:
db.collection.find(query,options).explain(options)
例如:
db.articledb.find({"name":"zhangquandan"}).explain()
观察结果可知,当前是使用的是集合扫描,没有用上索引
创建索引:
db.articledb.createIndex({"name":1})
再次查询测试:
db.articledb.find({"name":"zhangquandan"}).explain()
观察结果可知,当前是使用的是使用索引name_1进行查询
12.聚合查询
12.1管道操作符
聚合管道操作是将文档在一个管道处理完毕后,把处理的结果传递给下一个管道进行再次处理。聚合管道是使用不同的管道阶段操作器进行不同聚合操作,管道阶段操作器也可称为管道操作符。
常见管道操作符:
类别 | 操作符 | 说明 |
---|---|---|
分组 | $group |
将文档进行分组 |
限制 | $limit |
限制聚合管道返回的文档数 |
筛选 | $match |
用于过滤条件,只输出符合条件的文档 |
排序 | $sort |
对文档进行排序 |
投射 | $project |
修改文档结构(增加、删除字段)和名称 |
跳过 | $skip |
跳过指定数量的文档 |
$limit
:用于限制MongoDB聚合管道返回的文档数
语法如下:
db.COLLECTION_NAME.aggregate([{$limit:整型数字}]).pretty()
例如:查询前三条文档数据
db.user.aggregate([{$limit:3}])
- **
$match
**:用于过滤数据,只输出符合条件的文档
语法如下:
db.COLLECTION_NAME.aggregate([{$match:{<key>:<value>}}]).pretty()
例如:
db.user.aggregate([{$match:{"name":"zhangsan"}}])
- **
$sort
**:将输入的文档先进行排序,再输出,-1表示降序,1表示升序。
语法如下:
db.COLLECTION_NAME.aggregate([{$sort:{<key>:-1或1}}]).pretty()
例如:
db.user.aggregate([{$sort:{"age":1}}])
**
$project
**操作符:用于修改输入文档的结构(增加、删除字段等)和名称。语法如下:
db.COLLECTION_NAME.aggregate([{$project:{<key>:<value>}}]).pretty()
例如:
使用$project操作符,展示集合comment中的文档,并且文档均不包含字段_id
db.user.aggregate([{$project:{"_id":0}}])
- **
$skip
**操作符:在聚合管道中跳过指定数量的文档,并返回剩余的文档。
语法如下:
db.COLLECTION_NAME.aggregate([{$skip:整型数字}]).pretty()
例如:
db.user.aggregate([{$skip:2}])
- **
$group
**操作符:将集合中的文档进行分组,便于后续统计结果
语法如下:
db.COLLECTION_NAME.aggregate([{$group: { _id: <expression>, <field1>: { <accumulator1> : <expression1> }, ... } }]).pretty()
1._id+表达式用来做分组条件,也就是_id后面的内容与sql中group by后面的表达式的用途相同
2._id后面的 字段+accumulator操作符与sql中做完group by后在select后面的的聚合函数用途相同,例如:sum()、avg()、max()、min()。
如:按照年龄进行分组
db.user.aggregate([{$group:{_id:"$age"}}])
12.2管道表达式
管道阶段操作器的值被称为管道表达式,并且每个管道表达式都是一个文档结构,由字段名称、字段值和管道表达式组成。常见的管道表达式如下表。
$sum
语法如下:
db.COLLECTION_NAME.aggregate([{$group:{_id:"$<key1>”
,<key2>:{$sum:“$<key3>”}}}]).pretty()
$avg
语法如下:
db.COLLECTION_NAME.aggregate ([{$group:{_id:"$<key1>”
,<key2>:{$avg:“$<key3>”}}}]).pretty()
$min
db.COLLECTION_NAME.aggregate ([{$group:{_id:"$<key1>”
,<key2>:{$min:"$<key3>”}}}]).pretty()
$max
db.COLLECTION_NAME.aggregate ([{$group:{_id:"$<key1>”
,<key2>:{$max:$<key3>}}}]).pretty()
$push
语法如下:
db.COLLECTION_NAME.aggregate ([{$group:{_id:"$<key1>”
,<key2>:{$push:$<key3>}}}]).pretty()
$first
语法如下:
db.COLLECTION_NAME.aggregate ([{$group:{_id:"$<key1>”
,<key2>:{$first:$<key4>}}}]).pretty()
$last
语法如下:
db.COLLECTION_NAME.aggregate ([{$group:{_id:"$<key1>” <key2>:{$last:$<key5>}}}]).pretty()
2.8案例练习
2.8.1 文档简单操作1
- 创建newdb数据库
use newdb
- 创建集合mycollection
db.createCollection("mycollection");
- 在集合mycollection中插入数据
db.mycollection.insertOne({
title: "MangoDB入门学习",
description: "MongoDB是一个NoSQL数据库",
by: "数据库",
tags: ["MongoDB", "DataBase", "NoSQL"]
})
- 将标题为”MangoDB入门学习”更新为”MangoDB实践”
db.mycollection.updateOne({
"title": "MangoDB入门学习"
}, {
$set: {
"title": "MangoDB实践"
}
})
- 删除集合mycollection中的数据
db.myclollection.deleteMany({})
- 删除集合mycollection
db.myclollection.drop()
- 删除数据库newdb
db.newdb.drop()
2.8.2文档简单操作2
- 创建名称为自己姓名拼音缩写的数据库
use xha
- 在以自己姓名拼音缩写命名的数据库中创建集合students
db.createCollection("students")
- 在集合students中插入以下数据
db.students.insertMany([{
"name": "Jim",
"age": 25,
"email": "75431457@qq.com",
"score": {
"chinese": 89,
"math": 85,
"english": 99
},
"country": "USA",
"books": ["JS", "C++", "EXTJS", "MongoDB"]
}, {
"name": "Tom",
"age": 26,
"email": "214551267@qq.com",
"score": {
"chinese": 75,
"math": 77,
"english": 95
},
"country": "USA",
"books": ["PHP", "JAVA", "EXTJS", "C++"]
}, {
"name": "Lily ",
"age": 24,
"email": "344521234@qq.com",
"score": {
"chinese": 80,
"math": 82,
"english": 94
},
"country": "USA",
"books": ["JS", "JAVA", "C#", "MongoDB"]
}, {
"name": "李永",
"age": 25,
"email": "214556745@qq.com",
"score": {
"chinese": 96,
"math": 94,
"english": 90
},
"country": "China",
"books": ["JS", "JAVA", "EXTJS", "MongoDB"]
}, {
"name": "王敏",
"age": 23,
"email": "274524359@qq.com",
"score": {
"chinese": 99,
"math": 96,
"english": 97
},
"country": "China",
"books": ["JS", "C#", "PHP", "MongoDB"]
}, {
"name": "张力",
"age": 22,
"email": "232435456@qq.com",
"score": {
"chinese": 89,
"math": 97,
"english": 89
},
"country": "China",
"books": ["JS", "JAVA", "C++", "MongoDB"]
}, {
"name": "朴英俊",
"age": 27,
"email": "645434239@qq.com",
"score": {
"chinese": 36,
"math": 46,
"english": 55
},
"country": "Korea",
"books": ["JS", "JAVA", "EXTJS", "PHP"]
}, {
"name": "李贞贤",
"age": 24,
"email": "987555668@qq.com",
"score": {
"chinese": 35,
"math": 75,
"english": 64
},
"country": "Korea",
"books": ["JS", "C#", "EXTJS", "MongoDB"]
}, {
"name": "李慧英",
"age": 26,
"email": "435567778@qq.com",
"score": {
"chinese": 45,
"math": 63,
"english": 77
},
"country": "Korea",
"books": ["JS", "JAVA", "EXTJS", "MongoDB"]
}])
- 将李慧英的英语成绩修改为88
db.students.updateOne({
"name": "李慧英"
}, {
$set: {
"score.english": 88
}
})
- 删除姓名是李贞贤的学生信息
db.students.deleteOne({
"name": "李贞贤"
})
- 将张力的书架上的第二本书修改为“C#”
db.students.updateOne({
"name": "张力"
}, {
$set: {
"books.1": "C#"
}
})
- 给王敏的成绩中添加一门课“physical”,成绩为89
db.students.update({
"name": "王敏"
}, {
$set: {
"score.pyhsical": 89
}
})
- 删除集合students
db.students.drop()
- 删除数据库stu
db.xha.drop()
2.8.3文档简单操作3
- 统计中国学生的人数
db.students.find({"country":"China"}).count();
- 查询李姓学生的信息
db.students.find({"name":/^李/});
- 查询集合students中姓名为”王敏”的学生信息,结果中不显示_id,显示name,age
db.students.find({"name":"王敏"},{"name":1,"age":1,"_id":0});
- 查询语文成绩大于80小于95的文档
db.students.find({"score.chinese":{$gt:80,$lt:95}});
- 查询书架上有“MongoDB”这本书的学生的姓名和年龄
db.students.find({"books":{$in:["MongoDB"]}},{"name":1,"age":1});
- 查询书架上有“JAVA”或“JS”书的文档
db.students.find({$or:[{"books":"JAVA"},{"books":"JS"}]});
- 查询数学成绩小于60或大于90的学生的姓名
db.students.find({$or:[{"score.math":{$lt:60}},{"score.math":{$gt:90}}]},{"name":1,"_id":0});
- 查询英语成绩在60到90之间的学生的信息
db.students.find({$and:[{"score.english":{$gt:60}},{"score.english":{$lt:90}}]});
- 查询书架上有4本书的学生的信息
db.students.find({"books": {$size: 4}});
- 查询国籍是“USA”和“China”的学生的姓名
db.students.find({$or:[{"country":"China"},{"country":"USA"}]},{"name":1,"_id":0});
- 查询国籍是USA并且语文成绩大于80的学生的信息
db.students.find({$and:[{"country":"USA"},{"score.chinese":{$gt:80}}]});
2.8.4文档聚合操作
- 查询软件技术和移动应用开发专业学生的平均年龄
db.students.aggregate([{$match: {major: {$in: ["软件技术", "移动应用开发"]}}}, {
$group: {
_id: "$major",
age_avg: {$avg: {$toInt: "$age"}}
}
}])
- 查看计算机应用技术专业的学生,并按照学分进行降序的排序
db.students.aggregate([{$match: {major: "计算机应用技术"}}, {$sort: {score: -1}}]);
- 查看计算机应用技术专业男生和女生的总学分,最高学分和最低学分
db.students.aggregate([
{$match: {major: "计算机应用技术"}},
{$group: {_id: "$sex", total: {$sum: {$toInt: "$score"}}, max: {$max: "$score"}, min: {$min: "$score"}}}
])
- 查询各个专业学生的平均成绩,第一个人的成绩和最后一个人的成绩
db.students.aggregate([
{$group: {_id: "$major", avg: {$avg: {$toInt: "$score"}}, first: {$first: "$score"}, last: {$last: "$score"}}}
])
- 统计各个专业的学生人数并按人数从多到少排序
db.students.aggregate([
{$group: {_id: "$major", count: {$sum: 1}}},
{$sort: {count: -1}}
])
- 显示成绩排名第4和第5的学生的姓名,专业和成绩
db.students.aggregate([
{$sort: {score: -1}},
{$skip: 3},
{$limit: 2},
{$project: {_id: 0, name: 1, major: 1, score: 1}}
])
- 统计不同性别的学生姓名
db.students.aggregate([
{$group: {_id: "$sex", name: {$push: "$name"}}}
])
- 查询年龄大于19的男生、女生人数
db.students.aggregate([{$match: {$expr: {"age": {$gt: [{$toInt: "$age"}, 19]}}}}, {$group: {_id: "$sex", "人数": {$sum: 1}}}])
2.8.5索引操作
- 在集合students的字段age上创建单字段索引,并指定顺序为降序
db.students.createIndex({age: -1},{name: "age_index"},{background: true})
- 在集合students的字段major和字段credits上创建复合索引,指定字段major 为升序,字段credits为降序
db.students.createIndex({major: 1, credits: -1})
- 查询数据库stu中集合students的索引
db.students.getIndexes()
- 删除集合students字段age上的单字段索引
db.students.dropIndex("age_index")
- 删除集合students中的所有索引
db.students.dropIndexes()
2.8.6安全与访问控制
- 在admin数据库中创建管理员用户“useradmin”,密码为“123456”,拥有userAdminAnyDatabase角色
db.createUser({user:"useradmin",pwd:"123456",roles:[{role:"userAdminAnyDatabase",db:"admin"}]})
- 开启用户访问控制:修改配置文件mongod.cfg,重启MongoDB服务
- 使用管理员账户useradmin在数据库admin中创建一个用户“sturead”,密码“123456”,并且该用户只具有stu数据库的read权限
db.createUser({
user: "sturead",
pwd: "123456",
roles: [{
role: "read",
db: "admin"
}]
})
- 查看用户信息
db.getUser(用户名)
- 查看用户权限
db.getUser(用户名,{showPrivileges:true})
- 修改用户信息
db.updateUser("itcastUser",{roles:[{role:"read",db:“articledb"},
{role:"readAnyDatabase",db:"admin"}]})
- 删除用户角色
db.revokeRolesFromUser(用户名,[{role:"readAnyDatabase",db:"admin"}])
- 修改用户密码
db.changeUserPassword(用户名,密码)
- 删除用户
db.dropUser(用户名)
13.副本集
**MongoDB 副本集(Replica Set)是有自动故障恢复功能的主从集群,有一个Primary节点和一个或多个Secondary节点组成。**副本集没有固定的主节点,当主节点发生故障时整个集群会选举一个主节点为系统提供服务以保证系统的高可用。注意:这种方式并不能解决主节点的单点访问压力问题。
Automatic Failover
自动故障转移机制: 当主节点未与集合的其他成员通信超过配置的选举超时时间(默认为 10 秒)时,合格的辅助节点将调用选举以将自己提名为新的主节点。集群尝试完成新主节点的选举并恢复正常操作。
搭建副本集
- 创建数据目录
# 在安装目录中创建
- mkdir -p ../repl/data1
- mkdir -p ../repl/data2
- mkdir -p ../repl/data3
- 搭建副本集:单独操作 当前机器的ip地址
$ mongod --port 27017 --dbpath ../repl/data1 --bind_ip 0.0.0.0 --replSet myreplace/[121.5.167.13:27018,121.5.167.13:27019]
$ mongod --port 27018 --dbpath ../repl/data2 --bind_ip 0.0.0.0 --replSet myreplace/[121.5.167.13:27019,121.5.167.13:27017]
$ mongod --port 27019 --dbpath ../repl/data3 --bind_ip 0.0.0.0 --replSet myreplace/[121.5.167.13:27017,121.5.167.13:27018]
注意: –replSet 副本集 myreplace 副本集名称/集群中其他节点的主机和端口
- 配置副本集,连接任意节点
- use admin
- 初始化副本集
var config = { _id:"myreplace", members:[ {_id:0,host:"121.5.167.13:27017"}, {_id:1,host:"121.5.167.13:27018"}, {_id:2,host:"121.5.167.13:27019"}] } rs.initiate(config);//初始化配置
- 设置客户端临时可以访问
> rs.slaveOk(); 旧的 > rs.secondaryOk(); 新的
注意:当MongoDB副本集架构只剩一个节点时,整个节点是不可用的。单主不可写。
14.分片集群
有副本集为什么要使用分片集群呢?
副本集的特点是主从复制,实现故障的自动转移和架构高可用,能够对数据进行冗余备份。
但是不能够解决
单点压力、并发访问压力问题
,即数据的读写都要在主节点
操作。
==分片(sharding)是指将数据拆分,将其分散存在不同机器的过程,有时也用分区(partitioning)来表示这个概念,==将数据分散在不同的机器上,不需要功能强大的大型计算机就能存储更多的数据,处理更大的负载。
分片目的是通过分片能够增加更多机器来应对不断的增加负载和数据,还不影响应用运行。
MongoDB支持自动分片
,可以摆脱手动分片的管理困扰,集群自动切分数据做负载均衡
。
**MongoDB分片的基本思想就是将集合拆分成多个块,这些快分散在若干个片里,每个片只负责总数据的一部分,应用程序不必知道哪些片对应哪些数据,甚至不需要知道数据拆分了**,所以在分片之前会运行一个路由进程,mongos进程
,这个路由器知道所有的数据存放位置,应用只需要直接与mongos交互即可。mongos自动将请求转到相应的片上获取数据,从应用角度看分不分片没有什么区别。
分片集群架构
Shard: 用于存储实际的数据块,实际生产环境中一个shard server角色可由几台机器组个一个replica set承担,防止主机单点故障
Config Server:mongod实例,存储了整个 ClusterMetadata。
Query Routers: 前端路由,客户端由此接入,且让整个集群看上去像单一数据库,前端应用可以透明使用。
Shard Key: 片键,**设置分片时需要在集合中选一个键,用该键的值作为拆分数据的依据,这个片键称之为(shard key),片键的选取很重要,片键的选取决定了数据散列是否均匀**。
集群环境搭建
# 1.集群规划
- Shard Server 1:27017
- Shard Repl 1:27018
- Shard Server 2:27019
- Shard Repl 2:27020
- Shard Server 3:27021
- Shard Repl 3:27022
- Config Server :27023
- Config Server :27024
- Config Server :27025
- Route Process :27026
# 2.进入安装的 bin 目录创建数据目录
- mkdir -p ../cluster/shard/s0
- mkdir -p ../cluster/shard/s0-repl
- mkdir -p ../cluster/shard/s1
- mkdir -p ../cluster/shard/s1-repl
- mkdir -p ../cluster/shard/s2
- mkdir -p ../cluster/shard/s2-repl
- mkdir -p ../cluster/shard/config1
- mkdir -p ../cluster/shard/config2
- mkdir -p ../cluster/shard/config3
# 3.启动4个 shard服务
# 启动 s0、r0
> ./mongod --port 27017 --dbpath ../cluster/shard/s0 --bind_ip 0.0.0.0 --shardsvr --replSet r0/121.5.167.13:27018
> ./mongod --port 27018 --dbpath ../cluster/shard/s0-repl --bind_ip 0.0.0.0 --shardsvr --replSet r0/121.5.167.13:27017
-- 1.登录任意节点
-- 2. use admin
-- 3. 执行
config = { _id:"r0", members:[
{_id:0,host:"121.5.167.13:27017"},
{_id:1,host:"121.5.167.13:27018"},
]
}
rs.initiate(config);//初始化
# 启动 s1、r1
> ./mongod --port 27019 --dbpath ../cluster/shard/s1 --bind_ip 0.0.0.0 --shardsvr --replSet r1/121.5.167.13:27020
> ./mongod --port 27020 --dbpath ../cluster/shard/s1-repl --bind_ip 0.0.0.0 --shardsvr --replSet r1/121.5.167.13:27019
-- 1.登录任意节点
-- 2. use admin
-- 3. 执行
config = { _id:"r1", members:[
{_id:0,host:"121.5.167.13:27019"},
{_id:1,host:"121.5.167.13:27020"},
]
}
rs.initiate(config);//初始化
# 启动 s2、r2
> ./mongod --port 27021 --dbpath ../cluster/shard/s2 --bind_ip 0.0.0.0 --shardsvr --replSet r2/121.5.167.13:27022
> ./mongod --port 27022 --dbpath ../cluster/shard/s2-repl --bind_ip 0.0.0.0 --shardsvr --replSet r2/121.5.167.13:27021
-- 1.登录任意节点
-- 2. use admin
-- 3. 执行
config = { _id:"r2", members:[
{_id:0,host:"121.5.167.13:27021"},
{_id:1,host:"121.5.167.13:27022"},
]
}
rs.initiate(config);//初始化
# 4.启动3个config服务
> ./mongod --port 27023 --dbpath ../cluster/shard/config1 --bind_ip 0.0.0.0 --replSet config/[121.5.167.13:27024,121.5.167.13:27025] --configsvr
> ./mongod --port 27024 --dbpath ../cluster/shard/config2 --bind_ip 0.0.0.0 --replSet config/[121.5.167.13:27023,121.5.167.13:27025] --configsvr
> ./mongod --port 27025 --dbpath ../cluster/shard/config3 --bind_ip 0.0.0.0 --replSet config/[121.5.167.13:27023,121.5.167.13:27024] --configsvr
# 5.初始化 config server 副本集
- `登录任意节点 congfig server`
> 1.use admin
> 2.在admin中执行
config = {
_id:"config",
configsvr: true,
members:[
{_id:0,host:"121.5.167.13:27023"},
{_id:1,host:"121.5.167.13:27024"},
{_id:2,host:"121.5.167.13:27025"}
]
}
> 3.rs.initiate(config); //初始化副本集配置
# 6.启动 mongos 路由服务
> ./mongos --port 27026 --configdb config/121.5.167.13:27023,121.5.167.13:27024,121.5.167.13:27025 --bind_ip 0.0.0.0
# 7.登录 mongos 服务
> 1.登录 mongo --port 27026
> 2.use admin
> 3.添加分片信息
db.runCommand({ addshard:"r0/121.5.167.13:27017,121.5.167.13:27018",
"allowLocal":true });
db.runCommand({ addshard:"r1/121.5.167.13:27019,121.5.167.13:27020",
"allowLocal":true });
db.runCommand({ addshard:"r2/121.5.167.13:27021,121.5.167.13:27022",
"allowLocal":true });
> 4.指定分片的数据库
db.runCommand({ enablesharding:"baizhi" });
> 5.设置库的片键信息
db.runCommand({ shardcollection: "baizhi.users", key: { _id:1}});
db.runCommand({ shardcollection: "baizhi.emps", key: { _id: "hashed"}})
13.SpringBoot集成Mongodb
13.1环境搭建
- 引入pom依赖
<!--spring data mongodb-->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-mongodb</artifactId>
</dependency>
- 配置文件
spring:
data:
mongodb:
database: test
host: localhost
port: 27017
username: root
password: 123456
authentication-database: admin
#uri等同于下面的配置
uri: mongodb://admin:123456@localhost:27017/test?authSource=admin
# 副本集配置
uri: mongodb://admin:123456@localhost:27017,localhost:27018,localhost:27019/test?authSource=admin
使用时注入mongoTemplate
13.2创建删除集合
@Resource
private MongoTemplate mongoTemplate;
@Test
public void testCreate() {
if (mongoTemplate.collectionExists("mongodbTest")) {
System.out.println("collection exists");
return;
}
mongoTemplate.createCollection("mongodbTest");
System.out.println("collection created successfully");
}
@Test
public void testDrop() {
if (mongoTemplate.collectionExists("mongodbTest")) {
mongoTemplate.dropCollection("mongodbTest");
System.out.println("collection dropped");
} else {
System.out.println("collection not exists");
}
}
13.3添加文档
相关注解:
@Document
对应 类修饰范围: 用在类上
作用: 用来映射这个类的一个对象为 mongo 中一条文档数据
属性:(value 、collection )用来指定操作的集合名称
@Id
对应 要指定为_id的变量名修饰范围: 用在成员变量、方法上,只能出现一次
作用: 用来将成员变量的值映射为文档的_id 的值
@Field
对应 剩余变量名(变量名都按照类中属性名定义时,可以不指定,即同名时可不指定)修饰范围: 用在成员变量、方法上
作用: 用来将成员变量以及值映射为文档中一个key、value对
属性: ( name,value)用来指定在文档中 key 的名称,默认为成员变量名
@Transient
不参与文档转换修饰范围: 用在成员变量、方法上
作用 : 用来指定改成员变量,不参与文档的序列化