MongoDB

mongodb是基于JSON的分布式文件存储数据库。monggo是介于关系型和非关系型数据库。实现关系数据库中的事务,能实现大部分单表查询的大部分功能,支持索引。monggodb是基于json实现的数据库,json本身就是一种数据对象的描述方式。所以在mongodb中的数据均已对象方式存储,一个单独的对象,比如一个订单 一个用户 无需拆分 而是直接将对象插入数据库。

支持复制 故障恢复和分片
支持索引 聚合 关联

应用场景:

高并发场景 (集群 分片)
高效存储和访问 (大量的数据)
高扩展性和高可用性 (结构松散)

社交场景 朋友圈 微博 海量数据
游戏场景 高并发 延迟 高效存储
物流场景 一个订单 N个物流数据 直接数组存储

特点

     数据量大  
     频繁读写
     非金融级的严格场景

传统SQL 设计数据库 使用ER图,一个对象会拆解成N张带关联的表
符合第一第二第三范式 数据库的字段都不可再次拆分 每个字段都和主键强绑定。

极度简化 查询和存储代码,数据不需要拆分到多张表,
也插入不需要new N个model以及查询后的数据组装。

以basic为例,数据结构非常复杂,mysql难以建模和维护模型
basic设计数据非常大10MB多 在mysql引擎难以优化查询过程,
读写速度慢。

总结

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

基本概念

库 database

和传统关系型数据库中的库差不多的概念 用于隔离不同应用之间的数据

集合 collection

类型关系型数据库中的表 但集合没有固定的结构
比如第一版的数据库,表存在8个列,后来业务更新,多了3个字段,可以直接插入,只不过旧数据没有相关字段值,不需要任何额外操作。

文档 document

集合中的一行数据

字段 field

和关系型数据库中的列相对应,但字段类型可以不一致。
比如age`字段,第一条数据值是`28`,第二条是`"28"
和js中的语法类似,js中没有完整的class的概念,所以数据类型是无法固定的,属性(字段)的个数也无法固定

默认的三个库

admin库:默认系统库
local: 数据不会被复制 用于存储本地单台服务器的任意集合
默认有一张startup_log,服务器每次启动插入一条信息
config: 设置分配时 用于存储配置

默认的_id字段

每个文档都有个字段_id,不指定的时候自动生成ObjectId类型的值。
ObjectId包含了当前的时间信息,当前的线程id,硬件信息,就是开启了集群后也不会并发重复id。且当id是默认的ObjectId类型时,可以反推出创建时间,createTime字段也省了。

> ObjectId("62b0039481213bdbb648ae12").getTimestamp()
> 2022-06-20T05:20:20.000Z

基本操作

查询数据库

> show databases;
> show dbs;

切换/创建库

> use <db-name>;

当前db

> db;

查看集合

> show collections;
> show tables;

删除当前数据库

> db.dropDataBase();

创建集合(可不创建 支持插入数据自动创建)

> db.createCollection("集合名称")

删除集合

> db.users.drop();

插入

插入一般有3个方法,第一种方法实际也能插入多行数据

1.db.users.insert({name:'aa'})
2.db.users.insertMany([{name:'aa'},{name:'bb'}])
3.for (let i =0 ; i<100;i++){
     db.users.insert({name:'cc'})
}

从第三种方法可以看到,mongosb的shell其实就是个js引擎的实例。你所执行的都是js代码。

> typeof db
> 'object'
> let a = 1 + 1
> a
> 2

查询id字段 并格式化

find方法第一个参数是筛选条件,第二个是查询字段,参数值为1时表示返回,0为不返回。

> db.users.find({},['_id']).pretty()

排序
排序使用sort方法,可以使用多个字段排序
其中排序字段的值1为升序 -1为降序

> db.users.find().sort({"name":1})

分页

> db.users.find().sort({"name":1}).skip(3).limit(1)

等价于SQL

> limit 3,1

筛选
等于 {name:"aaa"}
小于 {age:{$lt:20}}
小于等于 {age:{$lte:20}}
大于 {age:{$gt:20}}
大于等于 {age:{$gte:20}}
不等于 {age:{$ne:20}}

OR

查询条件的默认关系是AND ,如果想使用OR则需要使用内置操作符 $OR

> db.users.find(
> {
>    $or:[
>      {key1:value},{key2:value}
>     ],
>    key3:value
> })

等价于SQL

> where (key1 = value or key2 = value) and key3 = value

模糊匹配 (正则实现)

> db.users.find({name:/你好/})

查询count

> db.users.count()
> db.user.find({}).count()

去重

> db.users.distinct("字段名")

$type

基于bson类型来检索集合中匹配的数据类型并返回结果
比如查询集合users中age字段类型是integer类型的数据

> db.users.find({age:{$type:2}})
> db.users.find({age:{$type:'string'}})
常用类型 代号
Double 1 (默认的数字值类型 包含整数和小数)
String 2
Object 3
Array 4
Binarydata 5
Object Id 7
Boolean 8
Date 9
Null 10
Regular Expression 11
JavaScript 13
32-bit integer 16
64-bit integer 18
TimeStamp 17

删除

> db.users.remove(<query>,{
>   justOne:<boolean>
> })

删除所有

> db.users.remove({})

按条件删除
如果是按id删除 则需要使用{_id:ObjectId("xxxx")}

> db.users.remove({_id:1})
> db.users.remove({_id:ObjectId("xxxx")})

更新

> db.users.update(<query>,<update>,{upsert,multi})
参数 含义
query 查询条件
update 更新字段
upsert 默认false,不存在则插入
multi 默认false, 只更新查到的第一条

先删除后插入 最终数据只剩age字段

> db.users.update({age:25},{age:27})

再原数据上更新,如下将所有age是26的更新才27

> db.users.update({age:26},{$set:{age:27}})

索引 index

有空间换时间 提高查询效率
没有索引时 需要全表扫描 数据量大非常耗时
索引是单独存储的有序的数据集
查询的排序会使用索引的排序结果

创建索引

> db.users.createIndex(keys,options)
  • options:
    • backgroud:是否后台执行 默认会阻塞整个数据库
    • unique 是否唯一
    • name: 索引名称
    • sparse: 是否对不存在的字段索引 开启的话 字段值不存在的行不会被查询到
    • v: 版本号 和mongodb版本号有关 一般不填写
    • weights: 相对于其他索引的权重值

      查看索引

      > db.users.getIndexes()

      查看索引大小

      > db.users.totalIndexSize()

      删除所有索引

      > db.users.dropIndexes()

      删除特定索引

      > db.users.dropIndexes("索引名称")

      复合索引

      包含多个字段的索引。 只要筛选字段都存在于索引字段中就可以使用索引。

      db.users.createIndex({title:1,age:-1}}))

聚合查询 aggregate

等价于Group By

_id指聚合后的数据的id列使用的值,当然这个值也是聚合字段
$sum值sum求和函数的操作符。total是聚合后的查询字段

按by_user分组后,求每个分组的数量

db.users.aggregate([{_id": "$by_user",total: {$sum:1}}])
常用函数 说明
$sum 求和
$avg 平均值
$max 最大值
$min 最小值
$push 将值加入数组中,可重复
$addToSet 将值加入数组中,重复则不加入
$first 取第一个
$last 取最后一个

整合MongoDB

```xml
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-mongodb</artifactId>
</dependency>


 ```yaml
spring:
  data:
   mongodb:
     uri: mongodb://127.0.0.1:27017/test

常用注解

注解 说明
@Document 用在类上,用于映射mongo中的一条文档数据
@Id 用于成员变量,方法,映射文档的_id 值
@Field 将成员变量映射成到mongo的字段
@Transient 用于成员变量和方法,表示不参与序列化

插入操作

// upsert operation, low efficiency
MongoTemplate.save(new User(), "my_collection")
// insert only
MongoTemplate.insert(new User(), "my_collection")
// batch insert
List user = new ArrayList<>();
MongoTemplate.insert(user, User.class);

查询操作

查询所有

MongoTemplate.findAll(User.class, "my_collection");

按id查询

MongoTemplate.findById(1, User.class);

复杂查询

MongoTemplate.find(new Query(), User.class);
// where name = 'nick'
manager.find(Query.query(Criteria.where("name").is("nick")), User.class);
// Criteria 组合成复杂查询条件  a = 'a' or (c ='d' and e ='f')
new Criteria().and("a").is("a").orOperator(Criteria.where("c").is("d").and("e").is("f"));

排序和分页

MongoTemplate.find(new Query().with(Sort.by(Sort.Order.asc("name"))).skip(0).limit(7), User.class)
MongoTemplate.find(new Query().with(Sort.by("aa").ascending().and(Sort.by("bb").descending())).skip(0).limit(7), User.class);

count

MongoTemplate.count(new Query(),"my_collection")

去重

name指去重字段 User.class 指返回值的封装类型

 MongoTemplate.findDistinct(new Query(),"name","my_collection",User.class);

JSON字符串方式查询

直接手写查询参数,此时可以设定返回指端

 Query query = new BasicQuery("{name: 'aaa'}");
 Query query = new BasicQuery("{$or:[{key1:value},{key2:value} ], key3:value}");
 Query query  =new  BasicQuery("{name: 'aaa'}","{name:1,age:0}");

更新操作

 Query query = Query.query(Criteria.where("a").is("b"));
 Update update = new Update();
 update.set("a", "c");
 // 更新查到的第一条
 MongoTemplate.updateFirst(query, update, "my_collection");
 // 更新所有查询到的
 MongoTemplate.updateMulti(query, update, "my_collection");
 // 更新或插入
 MongoTemplate.upsert(query, update, "my_collection");

删除

 MongoTemplate.remove(new Query(),"my_collection")

易错点 隐式类型转换

mysql中存在隐式类型转换,比如where查询一个int类型的字段可以传入字符串的值。
但是mongo是不强制要求一个列的值的数据类型,所以不存在隐式类型转换的。
比如某一行的数据列A类型是字符串,你传个数字是查不到数据的。

GridFs会在

默认mongo单个文档存在16MB的最大存储限制,当一个文档过大时,要么拆分存储,要么使用GridFs存储。当使用GridFs存储的时候,这个文档是以文件的形式存储的。

GridFs会在mongo创建名为gridfs的数据库,里面存在2个集合 fs.chunks和fs.files.chunks用于存储切割后的文档,默认切割后每个分片256k。files记录文件的元数据。

我们可以看到GridFs并不是全新的东西,它类似于语法糖,封装了自动数据切片的功能用于解决数据过大导致的性能的问题。

spring boot 中使用 GridFsTemplate 实现GridFs操作。
GridFsTemplate提供了store方法存储文件,返回文件id。
store的重载方法支持传入metaData用于存储一些原数据。
delete方法用于删除,find和findOne用于查询数据。
注意实现GridFs的时候,集合中只存在id列和文件本身。

String data = "{}";
InputStream inputStream = new ByteArrayInputStream(data.getBytes(StandardCharsets.UTF_8));
ObjectId id = gridFsTemplate.store(inputStream, "file_name", new User());
GridFSFile result = gridFsTemplate.findOne(Query.query(Criteria.where("_id").is(id)));
// User实例读取的时候变成Document(实际存入的时候已经转为Document)  Document实现了Map接口 所以数据直接get就能拿到。
Document metadata = result.getMetadata();
 ```

-----

### 副本集 Replica [ˈreplɪkə] Set

graph TD
A[Spring Boot Application] -->B(Driver)
B --> |write| D(Primary)
B --> |Read|D(Primary)
D --> |Replication| E[Secondary]
D --> |Replication| F[Secondary]



> 副本集是有自动故障恢复功能的主从集群。由一个主节点和多个从节点组成。没有固定的主节点,当主节点发生故障时集群会选举一个新的主节点。

> 主节点和从节点之间存在心跳机智,当主节点与从节点超过一定时间未通信,集群会尝试设置新的主节点。

> 通过解析操作日志,主节点的数据会自动同步到从节点。

> 客户端访问的总是直接点,从节点只是为了数据备份和故障转移
  并不能优化查询速度

### 分片集群 sharding cluster
分片指将数据拆分,将数据分散在不同的机器的过程,又是用分区来表示这个概念。此功能通过使用更多的机器来用对不断增加的负载和数据,而不影响应用运行。

mongo支持自动分片,摆脱手动分配的管理问题。基本思想是将集合拆成多块,分散到多个片中,每个片都只负责部分数据,应用程序不需要知道集合拆分成了几片,也不需要知道存入了哪几个片。

mongo内部存在一个路由进程,他会管理数据的分片信息。

本当の声を響かせてよ