MongoDB

一 MongoDB概述

1.1 什么是MongoDB

MongoDB是一个文档数据库(以 JSON 为数据模型),由C++语言编写,旨在为WEB应用提供可扩展的

高性能数据存储解决方案。

文档来自于“JSON Document”,并非我们一般理解的 PDF,WORD 文档。

MongoDB是一个介于关系数据库和非关系数据库之间的产品,是非关系数据库当中功能最丰富,最像

关系数据库的。它支持的数据结构非常松散,数据格式是BSON,一种类似JSON的二进制形式的存储格

式,简称Binary JSON ,和JSON一样支持内嵌的文档对象和数组对象,因此可以存储比较复杂的数据类

型。MongoDB最大的特点是它支持的查询语言非常强大,其语法有点类似于面向对象的查询语言,几

乎可以实现类似关系数据库单表查询的绝大部分功能,而且还支持对数据建立索引。原则上 Oracle

MySQL 能做的事情,MongoDB都能做(包括 ACID 事务)。

MongoDB在数据库总排名第5,仅次于Oracle、MySQL等RDBMS,在NoSQL数据库排名首位。从诞生

以来,其项目应用广度、社区活跃指数持续上升。

MongoDB概念与关系型数据库(RDBMS)非常类似:

数据库(database):最外层的概念,可以理解为逻辑上的名称空间,一个数据库包含多个不同名

称的集合。

集合(collection):相当于SQL中的表,一个集合可以存放多个不同的文档。

文档(document):一个文档相当于数据表中的一行,由多个不同的字段组成。

字段(field):文档中的一个属性,等同于列(column)。

索引(index):独立的检索式数据结构,与SQL概念一致。

id:每个文档中都拥有一个唯一的id字段,相当于SQL中的主键(primary key)。

视图(view):可以看作一种虚拟的(非真实存在的)集合,与SQL中的视图类似。从MongoDB

3.4版本开始提供了视图功能,其通过聚合管道技术实现。

聚合操作($lookup):MongoDB用于实现“类似”表连接(tablejoin)的聚合操作符。

尽管这些概念大多与SQL标准定义类似,但MongoDB与传统RDBMS仍然存在不少差异,包括:

  • 半结构化,在一个集合中,文档所拥有的字段并不需要是相同的,而且也不需要对所用的字段进行

    声明。因此,MongoDB具有很明显的半结构化特点。除了松散的表结构,文档还可以支持多级的

    嵌套、数组等灵活的数据类型,非常契合面向对象的编程模型。

  • 弱关系,MongoDB没有外键的约束,也没有非常强大的表连接能力。类似的功能需要使用聚合管

    道技术来弥补。

1.2 MongoDB技术优势

MongoDB基于灵活的JSON文档模型,非常适合敏捷式的快速开发。与此同时,其与生俱来的高可用、

高水平扩展能力使得它在处理海量、高并发的数据应用时颇具优势。

  • JSON 结构和对象模型接近,开发代码量低
  • JSON的动态模型意味着更容易响应新的业务需求
  • 复制集提供99.999%高可用
  • 分片架构支持海量数据和无缝扩容

简单直观:从错综复杂的关系模型到一目了然的对象模型

快速:最简单快速的开发方式

JSON模型之快速特性:

  • 数据库引擎只需要在一个存储区读写
  • 反范式、无关联的组织极大优化查询速度
  • 程序API自然,开发快速

灵活:快速响应业务变化

  • 多形性:同一个集合中可以包含不同字段类型的文档对象
  • 动态性:线上修改文档模型,修改时应用和数据库均无须下线。
  • 数据治理:支持使用JSON Schema来规范数据模式。在保证模式灵活动态的前提下,提供了数据治理能力

MongoDB优势:原生的高可用

  • Replica set -2 to 50个成员
  • 自恢复
  • 多中心容灾能力
  • 滚动服务-最小化服务终端

MongoDB优势:横向扩展能力

  • 需要的时候无缝扩展
  • 应用全透明
  • 多种数据分布策略
  • 轻松支持TB-PB数量级

1.3 MongoDB的应用场景

从目前阿里云 MongoDB 云数据库上的用户看,MongoDB 的应用已经渗透到各个领域:

  • 游戏场景,使用 MongoDB 存储游戏用户信息,用户的装备、积分等直接以内嵌文档的形式存储,

    方便查询、更新

  • 物流场景,使用 MongoDB 存储订单信息,订单状态在运送过程中会不断更新,以MongoDB 内嵌

    数组的形式来存储,一次查询就能将订单所有的变更读取出来;

  • 社交场景,使用 MongoDB 存储存储用户信息,以及用户发表的朋友圈信息,通过地理位置索引实

    现附近的人、地点等功能;

  • 物联网场景,使用 MongoDB 存储所有接入的智能设备信息,以及设备汇报的日志信息,并对这些

    信息进行多维度的分析;

  • 视频直播,使用 MongoDB 存储用户信息、礼物信息等;

  • 大数据应用,使用云数据库MongoDB作为大数据的云存储系统,随时进行数据提取分析,掌握行

    业动态。|

国内外知名互联网公司都在使用MongoDB:

1.4 如何考虑是否使用MongoDB

没有某个业务场景必须要使用MongoDB才能解决,但使用MongoDB通常能让你以更低的成本解决问题。如果你不清楚当前业务是否适合使用MongoDB,可以通过一下方面考虑是否使用MongoDB

  • 应用不需要复杂/长事务及join支持
  • 新应用,需求会变,数据模型无法确定,想快速迭代开发
  • 应用需要2000-3000以上的QPS
  • 应用需要TB甚至PB级别数据存储
  • 应用发展迅速,需要能快速水平扩展
  • 应用要求存储的数据永不丢失
  • 应用需要百分之99高可用
  • 应用需要大量的地理位置查询、文本查询

二 MongoDB快速开始

2.1 Linux下安装MondbDB

环境准备:

  • linux系统: centos7

  • 安装MongoDB社区版

下载地址:https://www.mongodb.com/try/download/community

1.解压

tar -zxvf mongodb-linux-x86_64-rhel70-4.4.15.tgz

2.配置环境变量

vim /etc/profile
export PATH=$PATH:$JAVA_HOME/bin:/usr/local/mongodb/mongodb-linux-x86_64-rhel70-4.4.15/bin:$PATH
source /etc/profile

3.查看mongodb版本

mongo -version

4.编写配置文件

在 MongoDB 根 目 录 中 创 建 data/db 目 录 , 用 于 存 储 数 据 库 文 件 数 据 , 并 且 再 创 建
data/logs/mongodb.log 用于存储日志

cd /usr/local/mongodb/
mkdir data/db data/logs -p
cd data/logs/
touch mongodb.log

在mongodb根目录下创建核心配置文件,并且追加配置内容如下:

systemLog:destination: filepath: /usr/local/mongodb/mongodb-linux-x86_64-rhel70-4.4.15/data/logs/mongod.log # log pathlogAppend: true #是否追加文件
storage:dbPath: /usr/local/mongodb/mongodb-linux-x86_64-rhel70-4.4.15/data/db # data directoryengine: wiredTiger #存储引擎journal: #是否启用journal日志enabled: true
net:bindIp: 0.0.0.0

5.启动

mongod -f mongodb.conf

出现一下内容代表启动成功

6.关闭mondodb

进入mongodb终端

use admin #奇幻到admin集合
db.showdownServer() #关闭数据库

2.2 Mongo Shell的使用

mongo是MongoDB的交互式JavaScript Shell界面,它为系统管理员提供了强大的界面,并为开发人员

提供了直接测试数据库查询和操作的方法

mongo --port=27017

JavaScript的支持

mongo shell是基于JavaScript语法的,MongoDB使用了SpiderMonkey作为其内部的JavaScript解释器

引擎,这是由Mozilla官方提供的JavaScript内核解释器,该解释器也被同样用于大名鼎鼎的Firefox浏览

器产品之中。SpiderMonkey对ECMA Script标准兼容性非常好,可以支持ECMA Script 6。可以通过下

面的命令检查JavaScript解释器的版本:

interpreterVersion()

mongo shell常用命令

  • - [x] | 命令 | 说明 |
    | :----------------------------: | :------------------------------: |
    | show dbs|show databases | 显示数据库列表 |
    | use 数据库名 | 切换数据库,如果不存在创建数据库 |
    | db.dropDatabase() | 删除数据库 |
    | show collections |show tables | 显示当前数据库的集合列表 |
    | db.集合名.stats() | 查看集合详情 |
    | db.集合名.drop() | 删除集合 |
    | show users | 显示当前数据库的用户列表 |
    | show roles | 显示当前数据库的角色列表 |
    | show profile | 显示最近发生的操作 |
    | load(“xxx.js”) | 执行一个JavaScript脚本文件 |
    | exit | quit() | 退出当前shell |
    | help | 查看mongodb支持哪些命令 |
    | db.help() | 查询当前数据库支持的方法 |
    | db.集合名.help() | 显示集合的帮助信息 |
    | db.version() | 查看数据库版本 |

数据库操作

1.查看数据库列表

show dbs;

2.切换指定数据库,不存在则创建

 use test;

3.删除当前数据库

db.dropDatabase()

集合操作

1.创建一个集合

db.createCollection('emp')

2.查看集合列表

show collections

3.删除集合

db.emp.drop()

注意:

​ 创建集合是可以填写可选项的

​ 当集合不存在时,向集合中插入文档也会创建集合

db.createCollection(name, options)

2.3 安全认证

创建管理员账号

# 设置管理员用户名密码需要切换到admin库
use admin
#创建管理员
db.createUser({user:"luoxue",pwd:"luoxue",roles:["root"]})
# 查看所有用户信息
show users
#删除用户
db.dropUser("luoxue")

常用权限

权限名 描述
read 允许用户读取指定数据库
readWrite 允许用户读写指定数据库
dbAdmin 允许用户在指定数据库中执行管理函数,如索引创建、删除,查看统计或访问system.profile
dbOwner 允许用户在指定数据库中执行任意操作,增、删、改、查等
userAdmin 允许用户向system.users集合写入,可以在指定数据库里创建、删除和管理用户
clusterAdmin 只在admin数据库中可用,赋予用户所有分片和复制集相关函数的管理权限
readAnyDatabase 只在admin数据库中可用,赋予用户所有数据库的读权限
readWriteAnyDatabase 只在admin数据库中可用,赋予用户所有数据库的读写权限
userAdminAnyDatabase 只在admin数据库中可用,赋予用户所有数据库的userAdmin权限
dbAdminAnyDatabase 只在admin数据库中可用,赋予用户所有数据库的dbAdmin权限
root 只在admin数据库中可用。超级账号,超级权限

用户认证

db.auth("luoxue","luoxue")

创建应用数据库用户

> use appdb
switched to db appdb
> db.createUser({user:"appdb",pwd:"luoxue",roles:["dbOwner"]})

默认情况下,MongoDB不会启用鉴权,我们需要以鉴权模式启动MongoDB

mongod -f mongodb.conf --auth

启用鉴权之后,连接MongoDB的相关操作都需要提供身份认证。

mongo 192.168.122.1:27017 -u luoxue -p luoxue --authenticationDatabase=admin

三 MongoDB文档操作

1.插入文档

新增单个文档

insertOne: 支持writeConcern

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

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

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

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

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

insert: 若插入的数据主键已经存在,则会抛 DuplicateKeyException 异常,提示主键重复,不保

存当前数据。

save: 如果 _id 主键存在则更新数据,如果不存在就插入数据

实战:

> db.createCollection('emp')
{ "ok" : 1 }
> db.emp.save({x:1})
WriteResult({ "nInserted" : 1 })
> db.emp.insert({x:2})
WriteResult({ "nInserted" : 1 })
> db.emp.insertOne({x:3})
{"acknowledged" : true,"insertedId" : ObjectId("62c3c5bba226137e06ebf075")
}

批量新增多个文档

insertMany:向指定集合中插入多条文档数据

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

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

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

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

> db.emp.insert([{x:4},{x:5]})
uncaught exception: SyntaxError: missing } after property list :
@(shell):1:25
> db.emp.insert([{x:4},{x:5}])
BulkWriteResult({"writeErrors" : [ ],"writeConcernErrors" : [ ],"nInserted" : 2,"nUpserted" : 0,"nMatched" : 0,"nModified" : 0,"nRemoved" : 0,"upserted" : [ ]
})
> db.emp.save([{x:6},{x:7}])
BulkWriteResult({"writeErrors" : [ ],"writeConcernErrors" : [ ],"nInserted" : 2,"nUpserted" : 0,"nMatched" : 0,"nModified" : 0,"nRemoved" : 0,"upserted" : [ ]
})
> db.emp.insertMany([{x:8},{x:9}])
{"acknowledged" : true,"insertedIds" : [ObjectId("62c3c839a226137e06ebf07c"),ObjectId("62c3c839a226137e06ebf07d")]
}

批量插入多条数据

1.编辑脚本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);

2.执行脚本

load("/usr/environment/mongodb/mongodb-linux-x86_64-rhel80-5.0.9/bin/book.js")

3.查看结果

db.books.find()

2.查询文档

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

db.collection.find(query, projection)

query :可选,使用查询操作符指定查询条件

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

db.books.find()

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

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

db.collection.findOne(query, projection)

条件查询

1.查询带有nosql标签的book文档:

db.books.find({tag:"nosql"})

2.按照id查询单个book文档:

db.books.find({_id:ObjectId("62c3ff4890608af870c7e5b4")})

3.查询分类为“travel”、收藏数超过60个的book文档:

db.books.find({type:"travel",favCount:{$gt:60}})

条件查询对照表:

SQL MQL
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}}

查询逻辑对照表

SQL MQL
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]}}

查询逻辑运算符

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

排序

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

1.查询type为travel的文档并按收藏数(favCount)降序返回

db.books.find({type:"travel"}).sort({favCount:-1})

分页查询

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

1.查询第三页的数据,每页大小2条

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

正则表达式匹配查询

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

1.使用正则表达式查找type包含 so 字符串的book

db.books.find({type:{$regex:"so"}})
db.books.find({type:{$regex:/so/}})

3.更新文档

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

db.collection.update(query,update,options)

query:描述更新的查询条件;

update:描述更新的动作及新的内容;

options:描述更新的选项

  • upsert: 可选,如果不存在update的记录,是否插入新的记录。默认false,不插入

  • multi: 可选,是否按条件查询出的多条记录全部更新。 默认false,只更新找到的第一条记录

  • writeConcern :可选,决定一个写操作落到多少个节点上才算成功。

更新操作符:

操作符 格式 描述
$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 }} 删除数组的第一个或最后一个元素

更新单个文档

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

db.books.update({_id:ObjectId("62c3ff4890608af870c7e5b3")},{$inc:{favCount:1}})

更新多个文档

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

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

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

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

  • updateOne:更新单个文档。

  • updateMany:更新多个文档。

  • replaceOne:替换单个文档。

upsert选项

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

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

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

replace语义

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

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

findAndModify

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

将某个book文档的收藏数(favCount)加1

db.books.findAndModify({ query:{_id:ObjectId("62c430a2bf3395e2b467f22b")}, update:{$inc:{favCount:1}} })

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

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

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

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

4.删除文档

使用remove删除文档

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

1.删除title为book-0的文档

 db.books.remove({title:"book-0"})

2.删除所有记录

db.books.remove({})

使用 delete 删除文档

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

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

返回被删除文档

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

四 Springboot整合MongoDB

1.环境准备

(1)依赖

 <dependencies><dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-data-mongodb</artifactId></dependency><dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-test</artifactId><scope>test</scope></dependency></dependencies>

(2)yml配置

spring:data:mongodb:database: testhost: 0.0.0.0port: 27017

(3)主启动

@SpringBootApplication
public class MongoDbApplication {public static void main(String[] args) {SpringApplication.run(MongoDbApplication.class,args);}
}

(4)测试

@SpringBootTest
public class MongoDbTest {@AutowiredMongoTemplate mongoTemplate;@Testpublic void test1(){boolean exists = mongoTemplate.collectionExists("emp");if(exists){mongoTemplate.dropCollection("emp");}mongoTemplate.createCollection("emp");}}

(5)结果

2.文档操作

相关注解:

  • @Document

    • 修饰范围: 用在类上
    • 作用: 用来映射这个类的一个对象为mongo中一条文档数据。
    • 属性:( value 、collection )用来指定操作的集合名称
  • @Id

    • 修饰范围: 用在成员变量、方法上
    • 作用: 用来将成员变量的值映射为文档的_id的值
  • @Field

    • 修饰范围: 用在成员变量、方法上
    • 作用: 用来将成员变量及其值映射为文档中一个key:value对。
    • 属性:( name , value )用来指定在文档中 key的名称,默认为成员变量名
  • @Transient

    • 修饰范围:用在成员变量、方法上
    • 作用:用来指定此成员变量不参与文档的序列化

实战

创建一个实体类

@Document("emp") //对应emp集合中的一个文档
@Data
@AllArgsConstructor
@NoArgsConstructor
public class Employee {@Id //映射文档中的_idprivate Integer id;@Field("username")private String name;@Fieldprivate int age;@Fieldprivate Double salary;@Fieldprivate Date birthday;}

插入文档

insert方法返回值是新增的Document对象,里面包含了新增后id的值。如果集合不存在会自动创建集合。通过Spring Data MongoDB还会给集合中多加一个class的属性,存储新增时Document对应Java中 类的全限定路径。这么做为了查询时能把Document转换为Java类型。

    @Testpublic void test2(){Employee employee = new Employee(1, "小明", 30,10000.00, new Date());//添加文档// sava: _id存在时更新数据//mongoTemplate.save(employee);// insert: _id存在抛出异常 支持批量操作mongoTemplate.insert(employee);List<Employee> list = Arrays.asList(new Employee(2, "张三", 21,5000.00, new Date()),new Employee(3, "李四", 26,8000.00, new Date()),new Employee(4, "王五",22, 8000.00, new Date()),new Employee(5, "张龙",28, 6000.00, new Date()),new Employee(6, "赵虎",24, 7000.00, new Date()),new Employee(7, "赵六",28, 12000.00, new Date()));mongoTemplate.insert(list,Employee.class);}

运行结果:

查询文档

Criteria是标准查询的接口,可以引用静态的Criteria.where的把多个条件组合在一起,就可以轻松地将多个方法标准和查询连接起来,方便我们操作查询语句。

1.查看所有文档

        System.out.println("==============查询所有文档==============");List<Employee> allEmployee = mongoTemplate.findAll(Employee.class);allEmployee.forEach(System.out::println);

2.根据id查询文档

        System.out.println("==============查询_id查询文档==============");Employee byId = mongoTemplate.findById(1, Employee.class);System.out.println(byId);

3.返回第一个文档

        System.out.println("==============返回第一个文档==============");Employee one = mongoTemplate.findOne(new Query(), Employee.class);System.out.println(one);

4.查询薪资大于8000的员工

        System.out.println("==============查询薪资大于等于8000的员工==============");Query query = new Query(Criteria.where("salary").gte(8000));List<Employee> employees = mongoTemplate.find(query, Employee.class);employees.forEach(System.out::println);

5.查询薪资大于4000小于10000的员工

        System.out.println("==============查询薪资大于4000小于10000的员工==============");Query query = new Query(Criteria.where("salary").gt(4000).lt(10000));List<Employee> employees = mongoTemplate.find(query, Employee.class);employees.forEach(System.out::println);

6.正则查询(模糊查询) java中正则不需要有//

        System.out.println("==============正则查询==============");Query query = new Query(Criteria.where("name").regex("张"));List<Employee> employees = mongoTemplate.find(query, Employee.class);employees.forEach(System.out::println);

7.查询年龄大于25并且薪资大于8000的员工

        System.out.println("==============查询年龄大于25并且薪资大于8000的员工==============");Criteria criteria = new Criteria();criteria.andOperator(Criteria.where("age").gt(25),Criteria.where("salary").gt(8000));Query query = new Query(criteria);List<Employee> employees = mongoTemplate.find(query, Employee.class);employees.forEach(System.out::println);

8.查询姓名是张三或者薪资大于8000的员工

        System.out.println("==============查询姓名是张三或者薪资大于8000的员工==============");Criteria criteria = new Criteria();criteria.orOperator(Criteria.where("name").is("张三"),Criteria.where("salary").gt(8000));Query query = new Query(criteria);List<Employee> employees = mongoTemplate.find(query, Employee.class);employees.forEach(System.out::println);

9.sort排序

        System.out.println("==============按照薪水排序==============");query.with(Sort.by(Sort.Order.desc("salary")));List<Employee> employees = mongoTemplate.find(query, Employee.class);employees.forEach(System.out::println);

10.skip limit 分页 skip用于指定跳过记录数,limit则用于限定返回结果数量。

        System.out.println("==============分页查询 每页两条记录 查询第二页==============");Query query = new Query();query.with(Sort.by(Sort.Order.desc("salary"))).skip(2).limit(2);List<Employee> employees = mongoTemplate.find(query, Employee.class);employees.forEach(System.out::println);

更新文档

在Mongodb中无论是使用客户端API还是使用Spring Data,更新返回结果一定是受行数影响。如果更新后的结果和更新前的结果是相同,返回0。

  • updateFirst() 只更新满足条件的第一条记录
  • updateMulti() 更新所有满足条件的记录
  • upsert() 没有符合条件的记录则插入数据

1.把薪资大于12000的第一条记录更新为13000

        Query query = new Query(Criteria.where("salary").gte(12000));System.out.println("==============更新前============");List<Employee> employees = mongoTemplate.find(query, Employee.class);employees.forEach(System.out::println);Update update = new Update();update.set("salary",13000);UpdateResult updateResult = mongoTemplate.updateFirst(query, update, Employee.class);System.out.println("==============更新影响行数============");System.out.println(updateResult.getModifiedCount());

2.把薪资大于7000的全部记录改为9000

        Query query = new Query(Criteria.where("salary").gte(7000));System.out.println("==============更新前============");List<Employee> employees = mongoTemplate.find(query, Employee.class);employees.forEach(System.out::println);Update update = new Update();update.set("salary",9000);UpdateResult updateResult = mongoTemplate.updateMulti(query, update, Employee.class);System.out.println("==============更新影响行数============");System.out.println(updateResult.getModifiedCount());

删除文档

1.条件删除

删除薪资大于9000的员工

        //条件删除Query query = new Query(Criteria.where("salary").gte(9000));DeleteResult remove = mongoTemplate.remove(query, Employee.class);System.out.println(remove.getDeletedCount());

2.全部删除

        DeleteResult remove = mongoTemplate.remove(new Query(), Employee.class);System.out.println(remove.getDeletedCount());

五 聚合操作

聚合操作处理数据记录并返回计算结果(诸如统计平均值,求和等)。聚合操作组值来自多个文档,可以对分组数据执行各种操作以返回单个结果。聚合操作包含三类:单一作用聚合、聚合管道、MapReduce。

  • 单一作用聚合:提供了对常见聚合过程的简单访问,操作都从单个集合聚合文档。

  • 聚合管道是一个数据聚合的框架,模型基于数据处理流水线的概念。文档进入多级管道,将文档转换为聚合结果。

  • MapReduce操作具有两个阶段:处理每个文档并向每个输入文档发射一个或多个对象的map阶段,以及reduce组合map操作的输出阶段。

1.单一作用聚合

MongoDB提供 db.collection.estimatedDocumentCount(), db.collection.count(), db.collection.distinct() 这类单一作用的聚合函数。 所有这些操作都聚合来自单个集合的文档。虽然这 些操作提供了对公共聚合过程的简单访问,但它们缺乏聚合管道和map-Reduce的灵活性和功能。

函数 描述
db.collection.estimatedDocumentCount() 忽略查询条件,返回集合或视图中所有文档的计数
db.collection.count() 返回与find()集合或视图的查询匹配的文档计数 。等同于 db.collection.find(query).count()构造
db.collection.distinct() 在单个集合或视图中查找指定字段的不同值,并在数组中返回结果。

1.检索books集合中所有文档的计数

db.books.estimatedDocumentCount()

2.查询收藏了超过50个的记录数量

db.books.count({favCount:{$gt:50}})

3.返回不同type的数组

db.books.distinct("type")

4.返回收藏数大于90的文档不同type的数组

db.books.distinct("type",{favCount:{$gt:90}})

注意:在分片群集上,如果存在孤立文档或正在进行块迁移,则db.collection.count()没有查询谓词可能导致计数不准确。要避免这些情况,请在分片群集上使用 db.collection.aggregate()方法。

2.聚合管道

(1)什么是MongoDB聚合框架

MongoDB 聚合框架(Aggregation Framework)是一个计算框架,它可以:

  • 作用在一个或几个集合上;
  • 对集合中的数据进行的一系列运算;
  • 将这些数据转化为期望的形式;

从效果而言,聚合框架相当于 SQL 查询中的GROUP BY、 LEFT OUTER JOIN 、 AS等。

(2)管道(Pipeline)和阶段(Stage)

整个聚合运算过程称为管道(Pipeline),它是由多个阶段(Stage)组成的, 每个管道:

  • 接受一系列文档(原始数据);
  • 每个阶段对这些文档进行一系列运算;
  • 结果文档输出给下一个阶段;

聚合管道操作语法

pipeline = [$stage1, $stage2, ...$stageN];
db.collection.aggregate(pipeline, {options})
  • pipelines 一组数据聚合阶段。除out、out、out、Merge和$geonear阶段之外,每个阶段都可以在管道中出现多次。
  • options 可选,聚合操作的其他参数。包含:查询计划、是否使用临时文件、 游标、最大操作时 间、读写策略、强制索引等等

(3)常用的管道聚合阶段

聚合管道包含非常丰富的聚合阶段,下面是最常用的聚合阶段

阶段 描述 SQL等价运算符
$match 筛选条件 WHERE
$project 投影 AS
$lookup 左外连接 LEFT OUTER JOIN
$sort 排序 ORDER BY
$group 分组 GROUP BY
skip/skip/skip/limit 分页
$unwind 展开数组
$graphLookup 图搜索
facet/facet/facet/bucket 分面搜索

(4)常规操作

数据准备

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 tagIdx2 = Math.floor(Math.random()*tags.length);
var favCount = Math.floor(Math.random()*100);
var username = "xx00"+Math.floor(Math.random()*10);
var age = 20 + Math.floor(Math.random()*15);
var book = {
title: "book-"+i,
type: types[typeIdx],
tag: [tags[tagIdx],tags[tagIdx2]],
favCount: favCount,
author: {name:username,age:age}
};
books.push(book)
}
db.books.insertMany(books);

1.$project

投影操作, 将原始字段投影成指定名称, 如将集合中的 title 投影成 name

db.books.aggregate([{$project:{name:"$title"}}])

$project 可以灵活控制输出文档的格式,也可以剔除不需要的字段

db.books.aggregate([{$project:{name:"$title",_id:0,type:1,author:1}}])

从嵌套文档中排除字段

db.books.aggregate([
... {$project:{name:"$title",_id:0,type:1,"author.name":1}}
... ])
#或者
db.books.aggregate([
{$project:{name:"$title",_id:0,type:1,author:{name:1}}}
])

2.$match

match用于对文档进行筛选,之后可以在得到的文档子集上做聚合,match用于对文档进行筛选,之后可以在得到的文档子集上做聚合,match用于对文档进行筛选,之后可以在得到的文档子集上做聚合,match可以使用除了地理空间之外的所有常规查询操作符,在实际应用中尽可能将match放在管道的前面位置。这样有两个好处:一是可以快速将不需要的文档过滤掉,以减少管道的工作量;二是如果在投射和分组之前执行match放在管道的前面位置。这样有两个好处:一是 可以快速将不需要的文档过滤掉,以减少管道的工作量;二是如果在投射和分组之前执行match放在管道的前面位置。这样有两个好处:一是可以快速将不需要的文档过滤掉,以减少管道的工作量;二是如果在投射和分组之前执行match,查询可以使用索引。

db.books.aggregate([{$match:{type:"technology"}}])

3.$count

计数并返回与查询匹配的结果数

db.books.aggregate([ {$match:{type:"technology"}}, {$count: "type_count"}
])

4.$group

按指定的表达式对文档进行分组,并将每个不同分组的文档输出到下一个阶段。输出文档包含一个_id字 段,该字段按键包含不同的组。 输出文档还可以包含计算字段,该字段保存由$group的_id字段分组的一些accumulator表达式的值。 $group不会输出具体的文档而只是统计信息。

{ $group: { _id: <expression>, <field1>: { <accumulator1> : <expression1> }, ...
} }
  • id字段是必填的;但是,可以指定id值为null来为整个输入文档计算累计值。
  • 剩余的计算字段是可选的,并使用运算符进行计算。
  • _id和表达式可以接受任何有效的表达式。

accumulator操作符

名称 描述 类比SQL
$avg 计算均值 avg
$first 返回每组第一个文档,如果有排序,按照排序,如果没有按照默认的存储的顺序的第一个文档。 limit 0,1
$last 返回每组最后一个文档,如果有排序,按照排序,如果没有按照默认 的存储的顺序的最后个文档。
$max 根据分组,获取集合中所有文档对应值得最大值。 max
$min 根据分组,获取集合中所有文档对应值得最小值。 min
$push 将指定的表达式的值添加到一个数组中。
$addToSet 将表达式的值添加到一个集合中(无重复值,无序)。
$sum 计算总和 sum
$stdDevPop 返回输入值的总体标准偏差(population standard deviation)
$stdDevSamp 返回输入值的样本标准偏差(the sample standard deviation)

group阶段的内存限制为100M。默认情况下,如果stage超过此限制,group阶段的内存限制为100M。默认情况下,如果stage超过此限制,group阶段的内存限制为100M。默认情况下,如果stage超过此限制,group将产生错误。但是,要允许处理大型数据集,请将allowDiskUse选项设置为true以启用$group操作以写入临时文件。

(1)book的数量,收藏总数和平均值

 db.books.aggregate([{$group:{_id:null,count:{$sum:1},pop:{$sum:"$favCount"},avg:{$avg:"$favCount"}}}])
{ "_id" : null, "count" : 50, "pop" : 2267, "avg" : 45.34 }

(2)统计每个作者的book收藏总数

db.books.aggregate([
{$group:{_id:"$author.name",pop:{$sum:"$favCount"}}}
])

(3)统计每个作者的每本book的收藏数

 db.books.aggregate([
{$group:{_id:{name:"$author.name",title:"$title"},pop:{$sum:"$favCount"}}}])

(4)每个作者的book的type合集

db.books.aggregate([
{$group:{_id:"$author.name",types:{$addToSet:"$type"}}}
])

$unwind

语法:

{$unwind:
{#要指定字段路径,在字段名称前加上$符并用引号括起来。
path: <field path>,
#可选,一个新字段的名称用于存放元素的数组索引。该名称不能以$开头。
includeArrayIndex: <string>,
#可选,default :false,若为true,如果路径为空,缺少或为空数组,则$unwind输出文档
preserveNullAndEmptyArrays: <boolean>
} }

(5).姓名为xx006的作者的book的tag数组拆分为多个文档

db.books.aggregate([
{$match:{"author.name":"xx006"}},
{$unwind:"$tag"}
])

(6):每个作者的book的tag合集

db.books.aggregate([
{$unwind:"$tag"},
{$group:{_id:"$author.name",types:{$addToSet:"$tag"}}}
])

数据准备:

db.books.insert([
{"title" : "book-51",
"type" : "technology",
"favCount" : 11,
"tag":[],
"author" : {"name" : "fox",
"age" : 28
}
},{"title" : "book-52",
"type" : "technology",
"favCount" : 15,
"author" : {"name" : "fox",
"age" : 28
}
},{"title" : "book-53",
"type" : "technology",
"tag" : [
"nosql",
"document"
],
"favCount" : 20,
"author" : {"name" : "fox",
"age" : 28
}
}])

includeArrayIndex

输出数组元素的数组索引

db.books.aggregate([
{$match:{"author.name":"fox"}},
{$unwind:{path:"$tag", includeArrayIndex: "arrayIndex"}}
])

preserveNullAndEmptyArrays

选项在输出中包含缺少size字段,null或空数组的文档

db.books.aggregate([
{$match:{"author.name":"fox"}},
{$unwind:{path:"$tag", preserveNullAndEmptyArrays: true}}
])

$limit

限制传递到管道中下一阶段的文档数

db.books.aggregate([
{$limit : 5 }
])

$skip

跳过进入stage的指定数量的文档,并将其余文档传递到管道中的下一个阶段

db.books.aggregate([
... {$skip : 5 }
... ])

$sort

对所有输入文档进行排序,并按排序顺序将它们返回到管道。

语法:

{ $sort: { <field1>: <sort order>, <field2>: <sort order> ... } }

要对字段进行排序,请将排序顺序设置为1或-1,以分别指定升序或降序排序,如下例所示:

db.books.aggregate([
{$sort : {favCount:-1,title:1}}
])

$lookup

Mongodb 3.2版本新增,主要用来实现多表关联查询, 相当关系型数据库中多表关联查询。每个输入待处理的文档,经过$lookup 阶段的处理,输出的新文档中会包含一个新生成的数组(可根据需要命名 新key )。数组列存放的数据是来自被Join集合的适配文档,如果没有,集合为空(即 为[ ])

语法:

db.collection.aggregate([{$lookup: {from: "<collection to join>",
localField: "<field from the input documents>",
foreignField: "<field from the documents of the from collection>",
as: "<output array field>"
}
})
属性 作用
from 同一个数据库下等待被Join的集合。
localField 源集合中的match值,如果输入的集合中,某文档没有 localField这个Key (Field),在处理的过程中,会默认为此文档含有 localField:null的键值对。
foreignField 待Join的集合的match值,如果待Join的集合中,文档没有foreignField值,在处理的过程中,会默认为此文档含有 foreignField:null的键值对。
as 为输出文档的新增值命名。如果输入的集合中已存在该值,则会覆盖掉

注意:null = null 此为真

其语法功能类似于下面的伪SQL语句:

SELECT *, <output array field>
FROM collection
WHERE <output array field> IN (SELECT *
FROM <collection to join>
WHERE <foreignField>= <collection.localField>);

案例:

数据准备

db.customer.insert({customerCode:1,name:"customer1",phone:"13112345678",address:
"test1"})
db.customer.insert({customerCode:2,name:"customer2",phone:"13112345679",address:
"test2"})
db.order.insert({orderId:1,orderCode:"order001",customerCode:1,price:200})
db.order.insert({orderId:2,orderCode:"order002",customerCode:2,price:400})
db.orderItem.insert({itemId:1,productName:"apples",qutity:2,orderId:1})
db.orderItem.insert({itemId:2,productName:"oranges",qutity:2,orderId:1})
db.orderItem.insert({itemId:3,productName:"mangoes",qutity:2,orderId:1})
db.orderItem.insert({itemId:4,productName:"apples",qutity:2,orderId:2})
db.orderItem.insert({itemId:5,productName:"oranges",qutity:2,orderId:2})
db.orderItem.insert({itemId:6,productName:"mangoes",qutity:2,orderId:2})

关联查询:

顾客和订单关联

db.customer.aggregate([
{$lookup: {from: "order",
localField: "customerCode",
foreignField: "customerCode",
as: "customerOrder"
}
}
])

订单和顾客和订单详情关联

db.order.aggregate([
{KaTeX parse error: Expected 'EOF', got '}' at position 105: … "curstomer" } }̲, {lookup: {
from: “orderItem”,
localField: “orderId”,
foreignField: “orderId”,
as: “orderItem”
}
}
])

(5)实战操作

1.统计每个分类的book文档数量

db.books.aggregate([
{$group:{_id:"$type",total:{$sum:1}}},
{$sort:{total:-1}}
])

2.标签的热度排行,标签的热度则按其关联book文档的收藏数(favCount)来计算

db.books.aggregate([
{$match:{favCount:{$gt:0}}},
{$unwind:"$tag"},
{$group:{_id:"$tag",total:{$sum:"$favCount"}}},
{$sort:{total:-1}}
])

  • $match阶段:用于过滤favCount=0的文档。

  • $unwind阶段:用于将标签数组进行展开,这样一个包含3个标签的文档会被拆解为3个条目。

  • group阶段:对拆解后的文档进行分组计算,group阶段:对拆解后的文档进行分组计算,group阶段:对拆解后的文档进行分组计算,sum:"$favCount"表示按favCount字段进行累 加。

  • $sort阶段:接收分组计算的输出,按total得分进行排序。

3.统计book文档收藏数[0,10),[10,60),[60,80),[80,100),[100,+∞)

db.books.aggregate([{$bucket:{groupBy:"$favCount",
boundaries:[0,10,60,80,100],
default:"other",
output:{"count":{$sum:1}}
}
}])

4.数据准备

导入邮政编码数据集 :https://media.mongodb.org/zips.json

使用mongoimport工具导入数据(https://www.mongodb.com/try/download/database-tools)

配置环境变量

导入数据:

mongoimport -h 0.0.0.0 -d appdb  -c zips --file E:\study\mongodb-database-tools-windows-x86_64-100.5.3\zips.json

h,–host :代表远程连接的数据库地址,默认连接本地Mongo数据库;

–port:代表远程连接的数据库的端口,默认连接的远程端口27017;

-u,–username:代表连接远程数据库的账号,如果设置数据库的认证,需要指定用户账号;

-p,–password:代表连接数据库的账号对应的密码;

-d,–db:代表连接的数据库;

-c,–collection:代表连接数据库中的集合;

-f, --fields:代表导入集合中的字段;

–type:代表导入的文件类型,包括csv和json,tsv文件,默认json格式;

–file:导入的文件名称 --headerline:导入csv文件时,指明第一行是列名,不需要导入;

1.返回人口超过1000万的州

db.zips.aggregate( [
{ $group: { _id: "$state", totalPop: { $sum: "$pop" } } },
{ $match: { totalPop: { $gt: 10*1000*1000 } } }
] )

2.返回各州平均城市人口

db.zips.aggregate( [
{ $group: { _id: { state: "$state", city: "$city" }, cityPop: { $sum: "$pop" }
} },
{ $group: { _id: "$_id.state", avgCityPop: { $avg: "$cityPop" } } },
{ $sort:{avgCityPop:-1}}
] )

3.按州返回最大和最小的城市

db.zips.aggregate( [
{ $group:
{_id: { state: "$state", city: "$city" },
pop: { $sum: "$pop" }
}
},
{ $sort: { pop: 1 } },
{ $group:
{_id : "$_id.state",
biggestCity: { $last: "$_id.city" },
biggestPop: { $last: "$pop" },
smallestCity: { $first: "$_id.city" },
smallestPop: { $first: "$pop" }
}
},
{ $project:
{ _id: 0,
state: "$_id",
biggestCity: { name: "$biggestCity", pop: "$biggestPop" },
smallestCity: { name: "$smallestCity", pop: "$smallestPop" }
}
},
{ $sort: { state: 1 } }
] )

3.MapReduce

MapReduce操作将大量的数据处理工作拆分成多个线程并行处理,然后将结果合并在一起。MongoDB 提供的Map-Reduce非常灵活,对于大规模数据分析也相当实用。

MapReduce具有两个阶段:

1.将具有相同Key的文档数据整合在一起的map阶段

2.组合map操作的结果进行统计输出的reduce阶段

MapReduce的基本语法

db.collection.mapReduce(
function() {emit(key,value);}, //map 函数
function(key,values) {return reduceFunction}, //reduce 函数
{out: <collection>,
query: <document>,
sort: <document>,
limit: <number>,
finalize: <function>,
scope: <document>,
jsMode: <boolean>,
verbose: <boolean>,
bypassDocumentValidation: <boolean>
}
)
  • map,将数据拆分成键值对,交给reduce函数
  • reduce,根据键将值做统计运算
  • out,可选,将结果汇入指定表
  • query,可选筛选数据的条件,筛选的数据送入map
  • sort,排序完后,送入map
  • limit,限制送入map的文档数
  • finalize,可选,修改reduce的结果后进行输出
  • scope,可选,指定map、reduce、finalize的全局变量
  • jsMode,可选,默认false。在mapreduce过程中是否将数 据转换成bson格式。
  • verbose,可选,是否在结果中显示时间,默认false
  • bypassDocmentValidation,可选,是否略过数据校验

1.统计type为travel的不同作者的book文档收藏数

db.books.mapReduce(
function(){emit(this.author.name,this.favCount)},
function(key,values){return Array.sum(values)},
{query:{type:"travel"},
out: "books_favCount"
}
)

六 Springboot整合MongoDB进行聚合操作

基于聚合管道mongodb提供的可操作的内容:

基于聚合操作Aggregation.group,mongodb提供可选的表达式

示例:

创建实体类

1.返回人口超过1000万的州

         //$groupGroupOperation groupOperation =Aggregation.group("state").sum("pop").as("totalPop");//$matchMatchOperation matchOperation = Aggregation.match(Criteria.where("totalPop").gte(10*1000*1000));//按顺序组合每一个聚合步骤TypedAggregation<Zips> typedAggregation =Aggregation.newAggregation(Zips.class,groupOperation, matchOperation);//执行聚合操作,如果不使用 Map,也可以使用自定义的实体类来接收数据AggregationResults<Map> aggregationResults =mongoTemplate.aggregate(typedAggregation, Map.class);//取出最终结果List<Map> mappedResults = aggregationResults.getMappedResults();for(Map map:mappedResults){System.out.println(map);}

2.返回各州平均城市人口

  GroupOperation groupOperation =Aggregation.group("state","city").sum("pop").as("cityPop");//$groupGroupOperation groupOperation2 =Aggregation.group("_id.state").avg("cityPop").as("avgCityPop");//$sortSortOperation sortOperation =Aggregation.sort(Sort.Direction.DESC,"avgCityPop");// 按顺序组合每一个聚合步骤TypedAggregation<Zips> typedAggregation =Aggregation.newAggregation(Zips.class,groupOperation, groupOperation2,sortOperation);//执行聚合操作,如果不使用 Map,也可以使用自定义的实体类来接收数据AggregationResults<Map> aggregationResults =mongoTemplate.aggregate(typedAggregation, Map.class);// 取出最终结果List<Map> mappedResults = aggregationResults.getMappedResults();for(Map map:mappedResults){System.out.println(map);}

3.按州返回最大和最小的城市

 //$groupGroupOperation groupOperation = Aggregation.group("state","city").sum("pop").as("pop");//$sortSortOperation sortOperation = Aggregation.sort(Sort.Direction.ASC,"pop");//$groupGroupOperation groupOperation2 = Aggregation.group("_id.state").last("_id.city").as("biggestCity").last("pop").as("biggestPop").first("_id.city").as("smallestCity").first("pop").as("smallestPop");//$projectProjectionOperation projectionOperation = Aggregation.project("state","biggestCity","smallestCity").and("_id").as("state").andExpression("{ name: \"$biggestCity\", pop: \"$biggestPop\" }").as("biggestCity").andExpression("{ name: \"$smallestCity\", pop: \"$smallestPop\" }").as("smallestCity").andExclude("_id");//$sortSortOperation sortOperation2 = Aggregation.sort(Sort.Direction.ASC,"state");// 按顺序组合每一个聚合步骤TypedAggregation<Zips> typedAggregation = Aggregation.newAggregation(Zips.class, groupOperation, sortOperation, groupOperation2,projectionOperation,sortOperation2);//执行聚合操作,如果不使用 Map,也可以使用自定义的实体类来接收数据AggregationResults<Map> aggregationResults = mongoTemplate.aggregate(typedAggregation, Map.class);// 取出最终结果List<Map> mappedResults = aggregationResults.getMappedResults();for(Map map:mappedResults){System.out.println(map);}

七 视图

MongoDB视图是一个可查询的对象,它的内容由其他集合或视图上的聚合管道定义。 MongoDB不会将视图内容持久化到磁盘。 当客户端查询视图时,视图的内容按需计算。 MongoDB可以要求客户端具有查询视图的权限。 MongoDB不支持对视图进行写操作。

作用:

数据抽象

保护敏感数据的一种方法

将敏感数据投影到视图之外

只读

结合基于角色的授权,可按角色访问信息

暂时省略

八 索引

1.索引介绍

索引是一种用来快速查询数据的数据结构。B+Tree就是一种常用的数据库索引数据结构,MongoDB 采用B+Tree 做索引,索引创建在colletions上。MongoDB不使用索引的查询,先扫描所有的文档,再匹配符合条件的文档。 使用索引的查询,通过索引找到文档,使用索引能够极大的提升查询效率。

MondoDB的索引结构

思考:MongoDB索引数据结构是B-Tree还是B+Tree?

B-Tree说法来源于官方文档,然后就导致了分歧:有人说MongoDB索引数据结构使用的是B-Tree,有的 人又说是B+Tree。

实际实现为B+树

索引的分类

按照索引包含的字段数量,可以分为单键索引和组合索引(或复合索引)

按照索引字段的类型,可以分为主键索引和非主键索引。

按照索引节点与物理记录的对应方式来分,可以分为聚簇索引和非聚簇索引,其中聚簇索引是指索 引节点上直接包含了数据记录,而后者则仅仅包含一个指向数据记录的指针。

按照索引的特性不同,又可以分为唯一索引、稀疏索引、文本索引、地理空间索引等

与大多数数据库一样,MongoDB支持各种丰富的索引类型,包括单键索引、复合索引,唯一索引等一 些常用的结构。由于采用了灵活可变的文档类型,因此它也同样支持对嵌套字段、数组进行索引。通过建立合适的索引,我们可以极大地提升数据的检索速度。在一些特殊应用场景,MongoDB还支持地理空间索引、文本检索索引、TTL索引等不同的特性。

2.索引操作

创建索引

创建索引语法格式

db.collection.createIndex(keys, options)
  • Key 值为你要创建的索引字段,1 按升序创建索引, -1 按降序创建索引
  • 可选参数列表如下:
Parameter Type Description
background Boolean 建索引过程会阻塞其它数据库操作,background可指定以后台方式创建索引,即增加 “background” 可 选参数。 “background” 默认值为false。
unique Boolean 建立的索引是否唯一。指定为true创建唯一索引。默认值为false
name string 索引的名称。如果未指定,MongoDB的通过连接索 引的字段名和排序顺序生成一个索引名称。
dropDups Boolean 3.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
# 创建索引后台执行
db.values.createIndex({open: 1, close: 1}, {background: true})
# 创建唯一索引
db.values.createIndex({title:1},{unique:true})

查看索引

#查看索引信息
db.books.getIndexes()
#查看索引键
db.books.getIndexKeys()

查看索引占用空间

db.collection.totalIndexSize([is_detail])
  • is_detail:可选参数,传入除0或false外的任意数据,都会显示该集合中每个索引的大小及总大 小。如果传入0或false则只显示该集合中所有索引的总大小。默认值为false。

删除所有

#删除集合指定索引
db.col.dropIndex("索引名称")
#删除集合所有索引
db.col.dropIndexes()

3.索引类型

单键索引

在某一个特定的字段上建立索引 mongoDB在ID上建立了唯一的单键索引,所以经常会使用id来进行查询; 在索引字段上进行精确匹配、排序以及范围查找都会使用此索引

db.books.createIndex({title:1})

对内嵌文档字段创建索引:

db.books.createIndex({"author.name":1})

复合索引

复合索引是多个字段组合而成的索引,其性质和单字段索引类似。但不同的是,复合索引中字段的顺序、字段的升降序对查询性能有直接的影响,因此在设计复合索引时则需要考虑不同的查询场景。

db.books.createIndex({type:1,favCount:1})

多键索引

在数组的属性上建立索引。针对这个数组的任意值的查询都会定位到这个文档,既多个索引入口或者键值引用同一个文档

准备数据

db.inventory.insertMany([
{ _id: 5, type: "food", item: "aaa", ratings: [ 5, 8, 9 ] },
{ _id: 6, type: "food", item: "bbb", ratings: [ 5, 9 ] },
{ _id: 7, type: "food", item: "ccc", ratings: [ 9, 5, 8 ] },
{ _id: 8, type: "food", item: "ddd", ratings: [ 9, 5 ] },
{ _id: 9, type: "food", item: "eee", ratings: [ 5, 9, 5 ] }
])

创建多键索引

 db.inventory.createIndex( { ratings: 1 } )

多键索引很容易与复合索引产生混淆,复合索引是多个字段的组合,而多键索引则仅仅是在一个字段上 出现了多键(multi key)。而实质上,多键索引也可以出现在复合字段上

# 创建复合多键索引
db.inventory.createIndex( { item:1,ratings: 1} )

注意: MongoDB并不支持一个复合索引中同时出现多个数组字段

嵌入文档的索引数组

db.inventory.insertMany([
{_id: 1,
item: "abc",
stock: [
{ size: "S", color: "red", quantity: 25 },
{ size: "S", color: "blue", quantity: 10 },
{ size: "M", color: "blue", quantity: 50 }
]
},
{_id: 2,
item: "def",
stock: [
{ size: "S", color: "blue", quantity: 20 },
{ size: "M", color: "blue", quantity: 5 },
{ size: "M", color: "black", quantity: 10 },
{ size: "L", color: "red", quantity: 2 }
]
},
{_id: 3,
item: "ijk",
stock: [
{ size: "M", color: "blue", quantity: 15 },
{ size: "L", color: "blue", quantity: 100 },
{ size: "L", color: "red", quantity: 25 }
]
}
])

在包含嵌套对象的数组字段上创建多键索引

db.inventory.createIndex( { "stock.size": 1, "stock.quantity": 1 } )

地理空间索引

在移动互联网时代,基于地理位置的检索(LBS)功能几乎是所有应用系统的标配。MongoDB为地理空间检索提供了非常方便的功能。地理空间索引(2dsphereindex)就是专门用于实现位置检索的一种特 殊索引。

案例:MongoDB如何实现“查询附近商家"?

db.restaurant.insert({restaurantId: 0,
restaurantName:"兰州牛肉面",
location : {type: "Point",
coordinates: [ -73.97, 40.77 ]
}
})

创建一个2dsphere索引

db.restaurant.createIndex({location : "2dsphere"})

查询附近10000米商家信息

db.restaurant.find( {location:{$near :{$geometry :{type : "Point" ,
coordinates : [ -73.88, 40.78 ]
} ,
$maxDistance:10000
}
}
} )

$near查询操作符,用于实现附近商家的检索,返回数据结果会按距离排序。

geometry操作符用于指定一个GeoJSON格式的地理空间对象,type=Point表示地理坐标点,coordinates则是用户当前所在的经纬度位置;geometry操作符用于指定一个GeoJSON格式的地理空间对象,type=Point表示地理坐标点, coordinates则是用户当前所在的经纬度位置;geometry操作符用于指定一个GeoJSON格式的地理空间对象,type=Point表示地理坐标点,coordinates则是用户当前所在的经纬度位置;maxDistance限定了最大距离,单位是米.

全文索引

MongoDB支持全文检索功能,可通过建立文本索引来实现简易的分词检索。

db.reviews.createIndex( { comments: "text" } )

text操作符可以在有textindex的集合上执行文本检索。text操作符可以在有text index的集合上执行文本检索。text操作符可以在有textindex的集合上执行文本检索。text将会使用空格和标点符号作为分隔符对检 索字符串进行分词, 并且对检索字符串中所有的分词结果进行一个逻辑上的 OR 操作。 全文索引能解决快速文本查找的需求,比如有一个博客文章集合,需要根据博客的内容来快速查找,则 可以针对博客内容建立文本索引。

案例:

数据准备

db.stores.insert(
[
{ _id: 1, name: "Java Hut", description: "Coffee and cakes" },
{ _id: 2, name: "Burger Buns", description: "Gourmet hamburgers" },
{ _id: 3, name: "Coffee Shop", description: "Just coffee" },
{ _id: 4, name: "Clothes Clothes Clothes", description: "Discount clothing"
},
{ _id: 5, name: "Java Shopping", description: "Indonesian goods" }
]
)

创建name和description的全文索引

db.stores.createIndex({name: "text", description: "text"}
Hash索引

不同于传统的B-Tree索引,哈希索引使用hash函数来创建索引。在索引字段上进行精确匹配,但不支持范围查询,不支持多键hash; Hash索引上的入口是均匀分布的,在分片集合中非常有用;

db.users. createIndex({username : 'hashed'})
通配符索引

MongoDB的文档模式是动态变化的,而通配符索引可以建立在一些不可预知的字段上,以此实现查询的加速。MongoDB 4.2 引入了通配符索引来支持对未知或任意字段的查询。

数据准备

db.products.insert([
{"product_name" : "Spy Coat",
"product_attributes" : {"material" : [ "Tweed", "Wool", "Leather" ],
"size" : {"length" : 72,
"units" : "inches"
}
}
},
{"product_name" : "Spy Pen",
"product_attributes" : {"colors" : [ "Blue", "Black" ],
"secret_feature" : {"name" : "laser",
"power" : "1000",
"units" : "watts",
}
}
},
{"product_name" : "Spy Book"
}
])

创建通配符索引

db.products.createIndex( { "product_attributes.$**" : 1 } )

测试

通配符索引可以支持任意单字段查询 product_attributes或其嵌入字段:

db.products.find( { "product_attributes.size.length" : { $gt : 60 } } )
db.products.find( { "product_attributes.material" : "Leather" } )
db.products.find( { "product_attributes.secret_feature.name" : "laser" } )

注意:通配符索引不兼容的索引类型或属性

4.索引属性

唯一索引

在现实场景中,唯一性是很常见的一种索引约束需求,重复的数据记录会带来许多处理上的麻烦,比如 订单的编号、用户的登录名等。通过建立唯一性索引,可以保证集合中文档的指定字段拥有唯一值。

# 创建唯一索引
db.values.createIndex({title:1},{unique:true})
# 复合索引支持唯一性约束
db.values.createIndex({title:1,type:1},{unique:true})
#多键索引支持唯一性约束
db.inventory.createIndex( { ratings: 1 },{unique:true} )

唯一性索引对于文档中缺失的字段,会使用null值代替,因此不允许存在多个文档缺失索引字段的情况。

对于分片的集合,唯一性约束必须匹配分片规则。换句话说,为了保证全局的唯一性,分片键必须作为唯一性索引的前缀字段。

部分索引

部分索引仅对满足指定过滤器表达式的文档进行索引。通过在一个集合中为文档的一个子集建立索引, 部分索引具有更低的存储需求和更低的索引创建和维护的性能成本。3.2新版功能。

部分索引提供了稀疏索引功能的超集,应该优先于稀疏索引。

db.restaurants.createIndex(
{ cuisine: 1, name: 1 },
{ partialFilterExpression: { rating: { $gt: 5 } } }
)

partialFilterExpression选项接受指定过滤条件的文档:

  • 等式表达式(例如:field: value或使用$eq操作符)
  • $exists: true
  • $gt, $gte, $lt, $lte
  • $type
  • 顶层的$and

稀疏索引

索引的稀疏属性确保索引只包含具有索引字段的文档的条目,索引将跳过没有索引字段的文档。 特性: 只对存在字段的文档进行索引(包括字段值为null的文档)

#不索引不包含xmpp_id字段的文档
db.addresses.createIndex( { "xmpp_id": 1 }, { sparse: true } )

TTL索引

在一般的应用系统中,并非所有的数据都需要永久存储。例如一些系统事件、用户消息等,这些数据随着时间的推移,其重要程度逐渐降低。更重要的是,存储这些大量的历史数据需要花费较高的成本,因此项目中通常会对过期且不再使用的数据进行老化处理。

通常的做法如下:

方案一:为每个数据记录一个时间戳,应用侧开启一个定时器,按时间戳定期删除过期的数据。

方案二:数据按日期进行分表,同一天的数据归档到同一张表,同样使用定时器删除过期的表。

对于数据老化,MongoDB提供了一种更加便捷的做法:TTL(Time To Live)索引。TTL索引需要声明 在一个日期类型的字段中,TTL 索引是特殊的单字段索引,MongoDB 可以使用它在一定时间或特定时 钟时间后自动从集合中删除文档。

# 创建 TTL 索引,TTL 值为3600秒
db.eventlog.createIndex( { "lastModifiedDate": 1 }, { expireAfterSeconds: 3600 }
)

对集合创建TTL索引之后,MongoDB会在周期性运行的后台线程中对该集合进行检查及数据清理工作。 除了数据老化功能,TTL索引具有普通索引的功能,同样可以用于加速数据的查询。 TTL 索引不保证过期数据会在过期后立即被删除。文档过期和 MongoDB 从数据库中删除文档的时间之间可能存在延迟。删除过期文档的后台任务每 60 秒运行一次。因此,在文档到期和后台任务运行之间的时间段内,文档可能会保留在集合中。

隐藏索引

隐藏索引对查询规划器不可见,不能用于支持查询。通过对规划器隐藏索引,用户可以在不实际删除索 引的情况下评估删除索引的潜在影响。如果影响是负面的,用户可以取消隐藏索引,而不必重新创建已 删除的索引。4.4新版功能。

# 建隐藏索引
db.restaurants.createIndex({ borough: 1 },{ hidden: true });
# 隐藏现有索引
db.restaurants.hideIndex( { borough: 1} );
db.restaurants.hideIndex( "索引名称" )
# 取消隐藏索引
db.restaurants.unhideIndex( { borough: 1} );
db.restaurants.unhideIndex( "索引名称" );

5.索引使用建议

1.为每一个查询建立合适的索引

这个是针对于数据量较大比如说超过几十上百万(文档数目)数量级的集合。如果没有索引MongoDB 需要把所有的Document从盘上读到内存,这会对MongoDB服务器造成较大的压力并影响到其他请求的执行。

2.创建合适的复合索引,不要依赖于交叉索引

如果你的查询会使用到多个字段,MongoDB有两个索引技术可以使用:交叉索引和复合索引。交叉索 引就是针对每个字段单独建立一个单字段索引,然后在查询执行时候使用相应的单字段索引进行索引交 叉而得到查询结果。交叉索引目前触发率较低,所以如果你有一个多字段查询的时候,建议使用复合索 引能够保证索引正常的使用。

3.复合索引字段顺序:匹配条件在前,范围条件在后(Equality First, Range After)

前面的例子,在创建复合索引时如果条件有匹配和范围之分,那么匹配条件(sport: “marathon”) 应该 在复合索引的前面。范围条件(age: <30)字段应该放在复合索引的后面。

4.尽可能使用覆盖索引

5.建索引要在后台运行

在对一个集合创建索引时,该集合所在的数据库将不接受其他读写操作。对大数据量的集合建索引,建 议使用后台运行选项 {background: true}

6.explain执行计划详解

通常我们需要关心的问题:

  • 查询是否使用了索引

  • 索引是否减少了扫描的记录数量

  • 是否存在低效的内存排序

MongoDB提供了explain命令,它可以帮助我们评估指定查询模型(querymodel)的执行计划,根据实际情况进行调整,然后提高查询效率。 explain()方法的形式如下:

db.collection.find().explain()

  • verbose 可选参数,表示执行计划的输出模式,默认queryPlanner

    模式名字 描述
    queryPlanner 执行计划的详细信息,包括查询计划、集合信息、查询条件、最佳执行计划、查询方式和 MongoDB 服务信息等
    exectionStats 最佳执行计划的执行情况和被拒绝的计划等信息
    allPlansExecution 选择并执行最佳执行计划,并返回最佳执行计划和其他执行计划的执行情况

queryPlanner

db.books.find({title:"book-1"}).explain("queryPlanner")

executionStats

executionStats 模式的返回信息中包含了 queryPlanner 模式的所有字段,并且还包含了最佳执行计划 的执行情况

字段名称 描述
winningPlan.inputStage 用来描述子stage, 并且为其父stage提 供文档和索引关键字
winningPlan.inputStage.stage 子查询方式
winningPlan.inputStage.keyPattern 所扫描的index内容
winningPlan.inputStage.indexName 索引名
winningPlan.inputStage.isMultiKey 是否是Multikey。如 果索引建立在array 上,将是true
executionStats.executionSuccess 是否执行成功
executionStats.nReturned 返回的个数
executionStats.executionTimeMillis 这条语句执行时间
executionStats.executionStages.executionTimeMillisEstimate 检索文档获取数据的 时间
executionStats.executionStages.inputStage.executionTimeMillisEstimate 扫描获取数据的时间
executionStats.totalKeysExamined 索引扫描次数
executionStats.totalDocsExamined 文档扫描次数
executionStats.executionStages.isEOF 是否到达 steam 结 尾,1 或者 true 代 表已到达结尾
executionStats.executionStages.works 工作单元数,一个查 询会分解成小的工作 单元
executionStats.executionStages.advanced 优先返回的结果数
executionStats.executionStages.docsExamined 文档检查数

allPlansExecution

allPlansExecution返回的信息包含 executionStats 模式的内容,且包含allPlansExecution:[]块

stage状态

状态 描述
COLLSCAN 全表扫描
IXSCAN 索引扫描
FETCH 根据索引检索指定文档
SHARD_MERGE 将各个分片返回数据进行合并
SORT 在内存中进行了排序
LIMIT 使用limit限制返回数
SKIP 使用skip进行跳过
IDHACK 对_id进行查询
SHARDING_FILTER 通过mongos对分片数据进行查询
COUNTSCAN count不使用Index进行count时的stage返回
COUNT_SCAN count使用了Index进行count时的stage返回
SUBPLA 未使用到索引的$or查询的stage返回
TEXT 使用全文索引进行查询时候的stage返回
PROJECTION 限定返回字段时候stage的返回

执行计划的返回结果中尽量不要出现以下stage:

COLLSCAN(全表扫描)

SORT(使用sort但是无index)

不合理的SKIP SUBPLA(未用到index的$or)

COUNTSCAN(不使用index进行count) }

九 复制集

MongoDB复制集的主要意义在于实现服务高可用,类似于Redis中的哨兵模式。

1.复制集的作用

  1. 数据写入主节点(Primary)时将数据复制到另一个副本节(Secondary)点上

  2. 主节点发生故障时自动选举出一个新的替代节点

在实现高可用的同时,复制集实现了其他几个作用

  • 数据分发:将数据从一个区域复制到另一个区域,减少另一个区域的读延迟

  • 读写分离:不同类型的操作分别在不同的节点上执行

  • 异地容灾:在数据中心故障时快速切换到异地

2.典型复制集结构

一个典型的复制集由三个或三个以上具有投票权的节点组成,其中一个主节点(Primary):接收写入操作,读操作和选举时投票,两个或多个从节点(Secondary):复制主节点上的新数据和选举时投票

3.数据复制

当一个修改操作,无论是插入,更新或删除,到达主节点时,它对数据的操作将被记录下来(经过一些必要的转换)。这些记录称为oplog

从节点通过从主节点上不断获取新进入主节点的oplog,并在自己的数据上回放,以此保持跟主 节点的数据一致。

4.通过选举完成故障恢复

具有投票权的节点之间两两互相发送心跳;

当5次心跳未收到时判断为节点失联

如果失联的是主机点,从节点会发起选举,选出新的主节点

如果失联的是从节点则不会产生新的选举

选举基于RAFT一致性算法实现,选举成功的必要条件是大多数投票节点存活

复制集中最多可以有50个节点,但具有投票权的节点最多7个

5.影响选举的因素

整个集群必须有大多数节点存活

被选举为主节点的节点必须

1.能够与多数节点建立连接

2.具有较新的oplog

3.具有较高的优先级(如果有配置)

6.复制集可选项

  • 是否具有投票权(v 参数): 有则参与投票
  • 优先级(priority参数):优先级越高的节点越优先成为主节点。优先级为0的节点无法成为主节 点,默认值为1
  • 隐藏(hidden参数):复制数据,但对应用不可见。隐藏节点可以具有投票权,但优先级必须为 0
  • 延迟(slaveDelay参数):复制 n 秒之前的数据,保持与主节点的时间差 从节点不建立索引( buildIndexes)

7.复制集注意事项

硬件:因为正常的复制集节点都有可能成为主节点,它们的地位是一样的,因此硬件配置上必须一致 为了保证节点不会同时宕机,各节点的硬件必须具有独立性。

软件:复制集各节点软件版本必须一致,以避免出现不可预知的问题

增加节点不会增加系统写性能

8.复制集的角色

副本集主要有两种类型和三种角色

两种类型:

  1. 主节点(Primary):数据操作的主要连接点,允许读和写操作
  2. 从节点(Secondaries):数据冗余备份节点,可以读或选举为主节点

角色:

MongoDB主要有三种角色

主要成员(Primary):主节点,主要接收所有的写操作

副本成员(Replicate):主从节点通过备份操作以维护相同的数据集。只支持读操作,不支持写操作。拥有选举能力

仲裁者(Arbiter):不保留任何数据的副本,只具有选举作用。副本成员也可以作为仲裁者。

8.复制集搭建

1.创建数据目录文件

mkdir db{1,2,3}

2.准备配置文件

复制集的每个mongod进程应该位于不同的服务器。我们现在在一台服务器上运行三个实例,所以要为它们各自配置

1.不同的端口: 28017, 28018,28019.

2.不同的数据目录

data/db1

data/db2

data/db3

3.不同的日志文件路径。实例中使用

/data/db1/mongod.log

/data/db2/mongod.log

/data/db3/mongod.log

data/db1/mongod.conf

systemLog:#日志输出为文件 destination: file#日志文件的路径 path: "/usr/environment/mongodb/mongodb-linux-x86_64-rhel80-5.0.9/bin/data/db1/logs/mongod.log"#mongod实例重新启动时,会将新条目附加到现有日志文件的末尾。 logAppend: true
storage: #数据目录。dbPath: "/usr/environment/mongodb/mongodb-linux-x86_64-rhel80-5.0.9/bin/data/db1" journal:#启用持久性日志enabled: true
processManagement:#启用守护进程模式。 fork: true#指定进程ID的文件位置pidFilePath: "/usr/environment/mongodb/mongodb-linux-x86_64-rhel80-5.0.9/bin/data/db3/logs/mongod.pid"
net:#服务实例绑定的IPbindIp: 0.0.0.0#绑定的端口 port: 27017
replication:#副本集的名称 replSetName: rs0

data/db2/mongod.conf

systemLog:#日志输出为文件 destination: file#日志文件的路径 path: "/usr/environment/mongodb/mongodb-linux-x86_64-rhel80-5.0.9/bin/data/db2/logs/mongod.log"#mongod实例重新启动时,会将新条目附加到现有日志文件的末尾。 logAppend: true
storage: #数据目录。dbPath: "/usr/environment/mongodb/mongodb-linux-x86_64-rhel80-5.0.9/bin/data/db2" journal:#启用持久性日志enabled: true
processManagement:#启用守护进程模式。 fork: true#指定进程ID的文件位置pidFilePath: "/usr/environment/mongodb/mongodb-linux-x86_64-rhel80-5.0.9/bin/data/db3/logs/mongod.pid"
net:#服务实例绑定的IPbindIp: 0.0.0.0#绑定的端口 port: 28018
replication:#副本集的名称 replSetName: rs0

data/db3/mongod.conf

systemLog:#日志输出为文件 destination: file#日志文件的路径 path: "/usr/environment/mongodb/mongodb-linux-x86_64-rhel80-5.0.9/bin/data/db3/logs/mongod.log"#mongod实例重新启动时,会将新条目附加到现有日志文件的末尾。 logAppend: true
storage: #数据目录。dbPath: "/usr/environment/mongodb/mongodb-linux-x86_64-rhel80-5.0.9/bin/data/db3" journal:#启用持久性日志enabled: true
processManagement:#启用守护进程模式。 fork: true#指定进程ID的文件位置pidFilePath: "/usr/environment/mongodb/mongodb-linux-x86_64-rhel80-5.0.9/bin/data/db3/logs/mongod.pid"
net:#服务实例绑定的IPbindIp: 0.0.0.0#绑定的端口 port: 28019
replication:#副本集的名称 replSetName: rs0

3.启动

[root@VM-12-2-centos db1]# mongod -f mongod.conf [root@VM-12-2-centos db2]# mongod -f mongod.conf [root@VM-12-2-centos db3]# mongod -f mongod.conf

4.配置复制集

进入db1的控制台

> rs.initiate()

默认第一次是从节点,按两次回车会变为主节点

5.查看集群状态

rs.conf()

6.28018/28019加入复制集

rs.add("lx:28018")
rs.add("lx:28019")

7.重新查看集群状态

8.测试

默认从节点是没有读权限的,我们需要开启

rs.secondaryOk()

主节点插入数据:

从节点查询数据

十 事务

1.写事务

(1)什么是writeConcern

writeConcern:决定了一个写操作落到多少个节点才算成功,writeConcern的取值有:

w:决定一个写操作落到多少个节点上才算成功,值包括:

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

  • 1~集群最大数据节点数: 写操作数据需要全部复制到配置节点上才算成功

  • majority:数据需要被复制到大多数节点上才算成功

默认:0

配置后写操作的程序将阻塞到写操作到达指定的节点数为止

j:MongoDB确认写操作已经写入磁盘上的日志(返回客户端成功失败)

  • true:刷到磁盘
  • false:内存

w:0

没有同步到从节点就返回成功

带来的问题:如果写入只是写入了主节点的内存,还没有入盘,也没有同步到从节点,那么如果主节点宕机就会发生数据丢失。

w:“mojority”

大多数节点都有数据后 才会返回成功

w:“all”

所有节点都同步到数据才返回成功

缺点:一个节点挂了,一直阻塞

(2)writeConcern实验

第九章已经搭建了一主二从的复制集,我们直接在主节点做实验

1.w:3

 db.emp.insert({count:1},{writeConcern:{w:3}})

2.w:4

db.emp.insert({count:1},{writeConcern:{w:4}})

3.w:“mojority”

db.emp.insert({count:2},{writeConcern:{w: "majority"}})

4.模拟网络延迟

把一个从节点设置为延迟节点

conf = rs.conf()
conf.members[1].secondaryDelaySecs=10 #延迟10s
conf.members[1].priority=0  #设置延迟节点必须设置这个
rs.reconfig(conf)

插入一条数据,10s后才看到插入成功

db.emp.insert({count:2},{writeConcern:{w:3}})

如果我们想设置写超时,也可以加wtimeout参数

db.emp.insert({count:2},{writeConcern:{w:3,wtimeout:2000}})

(3)小结

  • w:majority,要求过半数节点写入即代表成功,生产上重要数据可以这么设置,但不要设置等于有数据节点数,综合性能,majority写延迟是最优的

  • wtimeout默认值是0,如果不设置,当不满足写入节点数条件下的时候,会无限期阻塞

  • wtimeout设置时间后,在到达时间的时候会报警告,单数据已经写入,最好生产上监控起来

  • writeConcern 增加了写操作的延迟时间,但不会影响系统吞吐量,也不会显著增加服务器压力,因为写操作最终都会同步到所有节点上,只是影响了写操作的响应时间

2.读事务

在读取数据的时候我们需要关注一下两个问题:

  • 从哪里读?关注数据节点位置
  • 什么样的数据可以读?关注数据的隔离性

第一个问题由readPreference解决

第二个问题由readConcern解决

(1)什么是readPreference

readPreference决定使用哪一个节点来满足正在发起的读请求。可选值包括:

  • primary:只选择主节点

  • primaryPreferred:优先选择主节点,如果不可用则选择从节点,

  • secondary:只选择从节点

  • secondaryPreferred:优先选择从节点,如果从节点不可用则选择主节点

  • nearest:选择最近的节点

默认:primary

使用场景
  • 用户下订单后马上跳转到订单详情页-primary/primaryPreferred。主从复制延迟很低或基本无延迟场景

  • 查询历史订单-secondary/secondaryPreferred,不太要求时效性场景

  • 业务监控、报表统计-secondary,时效性不高,但是资源需求大,避免影响线上资源

  • 国际化业务,数据中心同步的数据,-nearest,读取最近节点,比如用户头像数据分发,

以上配置,只能控制读取一类节点,比如读取 secondary,secondary中可能有n个节点,还不能达到精准读取,如果实现控制精准读取,可以为副本集设置Tag

设置方法:
members[n].tags={ “”: “”, “”: “”,… }

DEMO:

conf.members[1].tags={region:“south”,datacenter:“A”}

如上图:

集群中5个节点,三个硬件较好,用户线上业务,2个节点硬件交叉,用于监控或生成报表
三个较好的节点上tags={item:“online”}
两个较差节点打上tags={item:“analyse”}
case:
db.collection.find({}).readPref( “secondary”, [ { “item”: “analyse”} ] )

readPreference的使用

方式一:Mongo Shell

db.collection.find().readPref(“secondary”)

方式二:MongoDB驱动API

MongoCollection.withReadPreference(ReadPreference readPref)

方式三:通过MongoDB 连接字符串

mongodb://192.168.254.211:28017,192.168.254.211:28018,192.168.254.211:28019/test?connect=replicaSet&slaveOk=true&replicaSet=rs0&readPreference=secondary

readPreference实验

从节点上执行以下命令,阻止数据同步
db.fsyncLock()
从节点上执行以下命令,恢复数据同步
db.fsyncUnlock()
观察数据读取情况

小节
  • 设置readPreference 时,应注意高可用问问题。如设置primary,主机点发生故障期间,业务系统将不可读取,如果业务允许,可以考虑primaryPreferred
  • 使用Tag也存在这样的问题,如果一个Tag只属于单一节点,发生故障的时候,将无节点可读,所以建议至少两个节点配置成同一个tag
  • tag使用时,遵循的原则考虑优先级,选举权因素,例如做监控或者报表的节点,不希望成为主节点,可设置成优先级0

(2)什么是readConcern

readConcern决定这个节点上的数据哪些是可读的,类似于关系型数据库的隔离级别。可选值有以下几个:

  • availavle: 读取所有可用的数据
  • local: 读取所有可用且数据当前分片的数据
  • majority: 读取在大多数节点上提交完成的数据
  • linearizable: 线性化读取文档
  • snapshot: 读取最近快照中心的数据

默认:local

available和local

在副本集中二者是没有区别的,主要的区别,体现在分片集群上,比如下面场景:

一个chunk x 正在从shard1向shard2迁移

在迁移过程中,部分数据(chunk x )会在两个分片中同时存在,但是源分片shard1仍是chunk x的负责方

  • 所有对x的读写操作仍然进入shard1
  • config中记录的信息x仍然属于shard1

如果此时读取shard2

  • local :只取由shard2负责的数据(不包括x)
  • availavle:shard2上有什么读什么(包括x)

majority

只读取大多数节点都提交了的数据。考虑以下场景

  • 集合中原有文档{x:0}
  • 将x的值改为1;

原理:

节点上维护了多个x版本,MVCC 的机制
MongoDB支持majority的readConcern级别。需要默认配置replication.enableMojorityReanConcer=true,否则不生效,其次,需要支持readCommited 级别的存储引擎支持,wiredtiger存储引擎作为当下mongoDB的默认存储引擎,是支持的。
当有数据更新时,MongoDB会起一个单独的snapshot线程,会周期性的对当前数据集进行snapshot,并记录快照的最新oplog时间戳,得到一个如下映射表:

只有确保 oplog 已经同步到大多数节点时,对应的 snapshot 才会标记为 commmited,用户读取时,从最新的 commited 状态的 snapshot 读取数据,就能保证读到的数据一定已经同步到的大多数节点。

primary 节点
secondary 节点在 自身oplog发生变化时,会通过 replSetUpdatePosition 命令来将 oplog 进度立即通知给 primary,另外心跳的消息里也会包含最新 oplog 的信息;通过上述方式,primary 节点能很快知道 oplog 同步情况,直到『最新一条已经同步到大多数节点的 oplog』,并更新 snapshot 的状态。比如当t2已经写入到大多数据节点时,snapshot1、snapshot2都可以更新为 commited 状态。(不必要的 snapshot也会定期被清理掉)

secondary 节点
secondary 节点拉取 oplog 时,primary 节点会将『最新一条已经同步到大多数节点的 oplog』的信息返回给 secondary 节点,secondary 节点通过这个oplog时间戳来更新自身的 snapshot 状态。

开启readConcern,snapshot存在内存中,增加了cache维护压力,对性能会有一定的损耗,性能大约降低30%左右.

readConcern:majority相当于关系数据库的READ COMMITTED

MongoDB中的回滚:

写操作到达大多数节点之前都是不安全的,一旦主节点崩溃,而从节点还没复制到该次操作,刚才的写操作就丢失了;
把一次写此操作当成一个事务,从事物的角度,可以认为事务被回滚了。
分布式系统的角度看,事务的提交,被提升到了分布式集群的多个节点提交,而不再是单节点上的提交

在可能发生回滚的前提下考虑脏读问题:
如果再一次写操作到达大多数节点前读取了这个写操作,然后因为系统故障而回滚则发生了脏读问题

使用readConcern:“majority”,可以有效避免脏读问题

readConcern:实现安全的读写分离:
场景:一般的主从架构,主节点写入,从节点读取,考虑如下语句
错误方式:
db.orders.insert({id:1,sku:“a”,price:50})
db.orders.find({id:1}).readPref(“secondary”)

以上两条语句有可能都不到刚写入的订单

正确方式:
db.orders.insert({id:1,sku:“a”,price:50},{writeConcern:{w:“majority”}})
db.orders.find({id:1}).readPref(“secondary”).readConcern(“majority”)

生产事故分析:
一般生产事故原因:
1-代码明显错误,未经过代码cr
2-不合操作规范的数据运维故障
3-代码深度bug,长久未发现,可能单一bug,可能组合bug负负得正,修改一处,导出其他bug
4-设计不合理,上线复杂,后台易用可用性差
5-机器容量评估不充分,流量打爆服务器
6-相关技术使用不规范,偶现生产事故,比如,主从读取设置不合理,从节点读取不到

linearizable

只读取大多数节点确认过的数据,和majority最大差别是保证绝对的操作线性顺序,在写操作自然时间后面发生的读,一定可以读到之前的写。

只对读取单个文档时有效
可能导致非常慢的读,因此总时间以配合使用maxTimeMS使用

linearizable Read Concern的实现原理是在read操作之前添加了一个空的写操作,
如果当前的primary是离线的,进程尚在,仍然可能有连接的查询,空的写操作就会失败(为什么失败思考?),从而被检查出来

sanpshot

相当于关系数据库的 SERIALIXABLE
只在多文档事务中生效
不出现脏读
不出现不可重复读
不出现幻读

因为所有的读都使用同一个快照,直到事务提交为止该快照才被释放

3.多文档事务

MongoDB虽然已经在4.2开始全面支持了多文档事务,但不代表可以毫无节制的使用他。通过合理的设计文档模型,可以规避大部分使用事务的必要性事务,就代表着锁,节点协调,网络沟通,性能降低,能不用,就不用。

1.java使用MongoDB的事务

try (ClientSession clientSession = client.startSession()) {clientSession.startTransaction();MongoCollection<Document> coll1 = client.getDatabase("mydb1").getCollection("foo");MongoCollection<Document> coll2 = client.getDatabase("mydb2").getCollection("bar");coll1.insertOne(clientSession, new Document("abc", 1));coll2.insertOne(clientSession, new Document("xyz", 999));clientSession.commitTransaction();
}

事务的隔离性
事务完成前,事务外的操作对该事务所做的修改,不可访问

db.test.insertMany([{x:1},{x:2}]);var session = db.getMongo().startSession();
session.startTransaction();
var coll = session.getDatabase("test").getCollection("test");
coll.updateOne({x:1},{$set:{y:2}});
coll.find();
db.test.find();
session.commitTransaction(); #提交事务
session.abortTransaction() #回滚事务

事务隔离性-可重复读实现

保证一个事务内查到的结果是一样的

var session = db.getMongo().startSession();
session.startTransaction({readConcern:{level:"snapshot"},writeConcern:{w:"majority"}});
var coll = session.getDatabase("test").getCollection("test");
coll.find();db.test.updateOne({x:1},{$set:{y:1}});
db.test.find();
session.commitTransaction();

事务的读写机制

MondoDB的事务错误处理机制不同于关系型数据库:

  • 当一个事务开启后,如果事务要修改的文档在事务外部被修改过,则事务修改这个文档的时候会触发Abort错误,因为此时的修改冲突了。
  • 这种情况下,只需要简单地重做事务
  • 如果一个事务已经开始修改文档,在事务之外尝试修改同一个文档,则事务外的修改会等待事务完成以后才能继续运行。

注意事项:

  • 可以实现和关系型数据库类似的事务场景
  • 必须使用和MongoDB兼容的驱动
  • 事务默认为60s内,60s以后失效
  • 涉及事务的分片不能使用仲裁节点
  • 事务会影响chunk的迁移效率。正在迁移的chunk可能造成事务失败
  • 多文档事务中读操作必须在主节点中
  • readConcern只应该在事务级别设置,不能设置在每次读写操作上.

十一 MongoDB最佳开发实践

1.MongoDB的连接

  • 关于驱动程序:总是选择与所用之 MongoDB 相兼容的驱动程序。这可以很容易地从驱动兼容对照表中查到;
  • 如果使用第三方框架(如 Spring Data),则还需要考虑框架版本与驱动的兼容性;
  • 关于连接对象 MongoClient:使用 MongoClient 对象连接到 MongoDB 实例时总是应该保证它单例,并且在整个生命周期中都从它获取其他操作对象。
  • 关于连接字符串:连接字符串中可以配置大部分连接选项,建议总是在连接字符串中配置这些选项;

// 连接到复制集

mongodb://节点1,节点2,节点3…/database?[options]

// 连接到分片集

mongodb://mongos1,mongos2,mongos3…/database?[options]

常见的连接字符串参数

  • maxPoolSize:连接池大小
  • Max Wait Time: 建议设置,自动杀掉太慢的查询
  • Write Concern: 建议 majority 保证数据安全
  • Read Concern:对于数据一致性要求高的场景适当使用

连接字符串节点和地址

无论对于复制集或分片集,连接字符串中都应尽可能多地提供节点地址,建议全部列出;

  • 复制集利用这些地址可以更有效地发现集群成员;
  • 分片集利用这些地址可以更有效地分散负载;
  • 连接字符串中尽可能使用与复制集内部配置相同的域名或 IP;

使用域名连接集群

在配置集群时使用域名可以为集群变更时提供一层额外的保护。例如需要将集群整体迁移到新网段,直接修改域名解析即可。另外,MongoDB 提供的 mongodb+srv:// 协议可以提供额外一层的保护。该协议允许通过域名解析得到所有 mongos 或节点的地址,而不是写在连接字符串中。

不要在 mongos 前面使用负载均衡

基于前面提到的原因,驱动已经知晓在不同的 mongos 之间实现负载均衡,而复制集则需要根据节点的角色来选择发送请求的目标。如果在 mongos 或复制集上层部署负载均衡;

  • 驱动会无法探测具体哪个节点存活,从而无法完成自动故障恢复;
  • 驱动会无法判断游标是在哪个节点创建的,从而遍历游标时出错;

结论:不要在 mongos 或复制集上层放置负载均衡器,让驱动处理负载均衡和自动故障恢复

2.查询和索引

  • 每一个查询都必须要有对应的索引
  • 尽量使用覆盖索引 Covered Indexes (可以避免读数据文件)
  • 使用 projection 来减少返回到客户端的的文档的内容

3.写入推荐

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

4.关于文档结构

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

5.关于分页-避免count的使用

尽可能不要计算总页数,特别是数据量大和查询条件不能完整命中索引时。考虑以下场景:假设集合总共有 1000w 条数据,在没有索引的情况下考虑以下查询:

db.coll.find({x: 100}).limit(50);

db.coll.count({x: 100});

  • 前者只需要遍历前 n 条,直到找到 50 条队伍 x=100 的文档即可结束;
  • 后者需要遍历完 1000w 条找到所有符合要求的文档才能得到结果。

为了计算总页数而进行的 count() 往往是拖慢页面整体加载速度的原因

6.处理分页问题 – 巧分页

避免使用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);

7.关于事务

使用事务的原则:

  • 无论何时,事务的使用总是能避免则避免;
  • 模型设计先于事务,尽可能用模型设计规避事务;
  • 不要使用过大的事务(尽量控制在 1000 个文档更新以内);
  • 当必须使用事务时,尽可能让涉及事务的文档分布在同一个分片上,这将有效地提高效率;

MongoDB从入门到高级的实战操作相关推荐

  1. 爬梯:MongoDB入门到高级到SpringBoot-API

    学习资料:黑马程序员 使用版本:mongodb 4.0.23 CentOs7 MongoDB 1. 入门 1.1 介绍 MongoDB是一个开源.高性能.无模式的文档型数据库,设计的初衷是用于简化开发 ...

  2. 2020年Linux的知识技术合集(基础入门到高级进阶)

    前言 本文介绍下Linux从入门到高级进阶的学习路线. 整个路线体系专注于服务器后台开发,知识点包括C/C++,Linux,Nginx,ZeroMQ,MySQL,Redis,fastdfs,Mongo ...

  3. php架构师视频教程,2020最新swoole视频教程推荐(从入门到高级)-php教程

    如下为各人整顿了php异步通讯框架Swoole的视频教程,没有需求从迅雷.baidu云之类的第三方网盘平台下载,全副正在线收费寓目.教程由浅入深,有php根底的人就能学习,从装置到案例解说,片面具体, ...

  4. Python入门篇-高级数据类型集合(set)和字典(dict)

    Python入门篇-高级数据类型集合(set)和字典(dict) 作者:尹正杰 版权声明:原创作品,谢绝转载!否则将追究法律责任. 一.集合(set) 1>.集合的特点 约定set 翻译为集合c ...

  5. mysql dba 视频_MySQL DBA入门到高级系列视频教程 [高清完整版]

    其他教程 当前位置:主页 > 编程教程 > 其他教程 > MySQL DBA入门到高级系列视频教程 [高清完整版] MySQL DBA入门到高级系列视频教程 [高清完整版] 教程大小 ...

  6. Java 从入门到高级学习路线

    前些天发现了一个巨牛的人工智能学习网站,通俗易懂,风趣幽默,忍不住分享一下给大家.点击跳转到教程. Java 从入门到高级学习路线 <一>1.Jvm 部分 Jvm 内存模型.Jvm 内存结 ...

  7. 【Golang 快速入门】高级语法:反射 + 并发

    Golang 快速入门 Golang 进阶 反射 变量内置 Pair 结构 reflect 结构体标签 并发知识 基础知识 早期调度器的处理 GMP 模型 调度器的设计策略 并发编程 goroutin ...

  8. MongoDB 快速入门实战教程最新版

    在上一篇 <MongoDB 实战教程:数据库与集合的 CRUD 操作篇> 中,我们学习了MongoDB 与 NoSQL 的关系. MongoDB 的安装.数据类型.MongoShell.创 ...

  9. ❤️《Vue前端基础框架集合从入门到高级》(小白也可学,建议收藏)❤️

    <Vue前端基础框架集合从入门到高级>,小白也可学 文章目录 <Vue前端基础框架集合从入门到高级>,小白也可学 ❤️一.前端核心分析 ❤️1.1.概述 ❤️1.2.前端三要素 ...

  10. 为什么总学不好PS?300集PS从入门到高级自学教程,全面且系统

    相信有很多的小伙伴在自学PS设计修图的过程中,一定会经常遇见这样的问题:无论是看别人制作短视频也好,还是收集许多的设计元素也罢,到头来只感觉自己的PS只会个皮毛. 300集PS从入门到高级自学教程+插 ...

最新文章

  1. python os获取文件大小_Python3基础 os.path.getsize 获得文件的大小
  2. 新冠轻症也会导致大脑退化,牛津大学最新研究登上Nature
  3. java 做计算器 百度云_用Java做一个简单的计算器
  4. vue 使用font-awesome
  5. Docker容器的备份和还原
  6. 2017 Alistair Croll 中国行:中西对话精益数据实践前瞻
  7. Android的服务(Service)(三)Service客户端的绑定与跨进程
  8. 自定义UITableViewCell需注意的问题
  9. 从BMW Vision iNEXT 看宝马如何进军自动驾驶
  10. java打印倒立直角三角形
  11. 《出发吧一起》第二阶段个人总结——Day01
  12. 二维码_encode与decode
  13. openresty入门 方法及指令
  14. 一次历时两周的实习生笔试
  15. 满格信号服务器开小差,明明信号满格,网络却很慢?教你三招解决!
  16. 开利网络拜访普恺图酒业总部,品高端酒,探共赢合作方案
  17. 4.File类、Lambda表达式、JAVA IO
  18. C语言fgets()函数
  19. 2015暑假集训总结
  20. [转贴]关于《功夫》一篇很好的评论

热门文章

  1. OpenHarmony学习笔记——编辑器访问Linux服务器进行编译
  2. 计算机怎么关闭u盘系统还原,电脑系统还原错误怎么办
  3. springboot+mybatis事务管理
  4. 网页设计期末作品_平面、网页、UI设计师该如何做作品集?
  5. 南京大学计算机科学与技术,南京大学计算机科学与技术系简介
  6. 机器学习中最常使用的10种数据编码方式
  7. 数据分析,怎么做才够“精准”
  8. LeetCode-618. 学生地理信息报告(困难)行转列
  9. 如何从0到1进行电商平台订单系统的搭建?
  10. linux触摸屏校准命令,Linux 触摸屏校准