MongoDB入门

适用于mongodb的业务场景:

  • 数据量大
  • 写入操作频繁(读写都很频繁)
  • 价值较低的数据,对事物要求不高(mongodb对于事物的支持不是很好)

什么时候选择mongodb:

  • 应用不需要事物及复杂的join支持
  • 新应用,需求会变,数据模型无法确定,想快速迭代开发
  • 应用需要高读写速率,需要2000-3000以上的读写QPS
  • 应用需要TB及PB级别的数据存储(维护成本较mysql低)
  • 应用要求存储的数据不丢失
  • 应用需要99.9999…%高可用
  • 应用需要大量的地理位置查询、文本查询

如果上述有一个符合就可以考虑使用mongodb,2个以上直接选择使用mongodb就行

image.png

image.png

基础mongodb命令

1
2
3
4
mongod --dbpath=../data/db    // 在debug时可以这样启动

mongod -f ./mongod.conf		// 以配置文件的方式启动mongodb
mongod --config ./mongod.conf
1
2
mongo 或
mongo --host=127.0.0.1 --port=27017   // 命令行连接mongodb,只mongo则是连接本地27017

配置文件内容:

image.png

1
2
3
4
关闭mongodb可以使用kill,但可能会出现数据错误,因此可以进入mongodb来关闭,
mongo --port 27017
use admin
db.shutdownServer()

CRUD

数据库操作

在每个集合中都会存在一个 _id,这个是mongdb的主键,它自己生成的

显示数据库

1
2
3
show dbs   或者  show databases   显示所有数据库

db   显示当前所处数据库

选择和创建数据库

1
use 数据库名称

如果库不存在则自动创建,有则切换到此库

要注意的是,在mongodb中,要新建了一个集合后才会真正的创建一个库。

原因是use后这个库会先存在于内存中,只有当往这个库中写入了数据,mongodb才会持久化这个库到磁盘中。

删除数据库

1
2
db.dropDatabase()
删除当前所在的库,因此删除前需要先use

默认库的作用

  • admin: 从权限的角度来看,这就类似于mysql的root数据库,如果将一个用户添加到这个数据库中,这个用户就自动继承所有数据库的权限。一些特定的服务器命令也只能在这个数据库中运行,比如关闭服务器。
  • local: 这个数据库中的数据永远不会被复制,作用于集群,集群之间是会相互复制彼此的数据,当数据存于这个库中时,数据就不会被集群中的其他库所复制,可以用来存储限于本地单台服务器的任意集合。
  • config: 当mongodb用于分片设置时,config数据库在内部使用,用于保存分片的相关信息。

集合操作

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

1
2
db.createCollection(name)
显式创建名称为name的集合
1
show tables 或 show collections  查看当前库中的表

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

1
2
db.table.drop()
删除名为table的集合,删除成功返回true,反之false

文档基本CRUD

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

单个文档的插入

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

1
2
3
4
5
6
7
8
# collection代表集合的名称
db.collection.insert(
	<document or array of ducuments>,
	{
		writeConcern:<document>,
		ordered:<boolean>
	}
)

image.png

1
2
3
4
5
db.comm.insertOne({"name":"zs","age":11})
往comm集合中插入一条数据

db.comm.insertMany([{"name":"zs","age":11},{"name":"ww","age":12}])
往comm集合中插入多条数据

要注意的是:当插入多条时如果某条数据插入失败,将会终止插入,但已经插入成功的数据不会回滚掉。

因此批量插入时最好可以加上try-catch来进行异常捕捉处理

1
2
3
4
5
try { 
	db.comm.insertMany([{"name":"zs","age":11},{"name":"ww","age":12}])
} catch(e){
	print(e)
}

文档的查询

基本查询

1
2
db.comm.find()
查询comm中的所有数据

此时会发现在文档中会有一个叫_id的字段,这个相当于MySQL的主键ID,在插入文档时没有指定该字段则MongoDB会自动创建,其类型是ObjectID类型。当然在插入文档时也可以指定_id为自己设置的值,建议不要自己指定_id,因为重复后会很麻烦。

1
2
db.comm.find({name:"ww"})
根据条件进行查询
1
2
3
4
5
db.comm.findOne(name:"ww")
只获取第一条数据,相当于mysql的limit

mongodb的find().pretty()
使得查询出来的数据在命令行中更加美观的显示,不至于太紧凑。

image.png

image.png

image.png

投影查询

相当于mysql的select id,name from user,即只显示指定的字段,其中value为1则表示显示此key字段,为0则为不显示,_id默认是显示的,所以必须设置为0让其不显示,其他字段只要没有显式设置,默认都是不显示

1
db.comm.find({},{name:1,_id:0})

聚合查询

image.png

image.png

image.png

image.png

示例

image.png

image.png

image.png

lookup只支持left outer join

文档的更新

1
2
db.collection.update(query,update,options)
query是查询条件,update是修改内容,options是附加的一些选项

覆盖修改

1
db.comm.update({name:"ww"},{age:11})

update字段的age可以为文档中不存在的值,此时这个文档除了age和_id,其他字段都不见了,此为覆盖修改

局部修改

1
db.comm.update({name:"ww"},{$set:{age:123}})

只修改指定的字段,其他字段不变

批量修改

覆盖修改和局部修改都只会修改一个文档,必须使用批量修改来改集合中满足条件的所有文档

1
2
3
db.comm.updateMany({name:"zs"},{$set:{age:12}})
OR
db.comm.update({name:"zs"},{$set:{age:13}},{multi:true})

列值增长的修改

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

1
2
3
db.comm.updateMany({name:"ww"},{$inc:{age:2}})

将集合中name=ww的所有文档的age加2

文档的删除

1
2
3
4
5
db.comm.deleteOne({agess:165})
删除comm表中age=165的最前面的一条文档

db.comm.deleteMany({agess:165})
删除comm表中age=165的所有文档
1
2
db.comm.remove({})
删除该集合中的所有文档,慎用!

文档的分页查询

统计查询

1
2
3
4
5
db.comm.count({age:11})
查询age=11的文档的个数

db.comm.count()
查询该集合总共有多少个文档

分页列表查询

可以使用limit()方法来读取指定数量的数据,使用skip()方法来跳过指定数量的数据。limit默认是20,skip默认是0

1
db.comm.find().limit(2).skip(1)

排序查询

sort()方法可以通过参数指定排序的字段来对数据进行排序,并用 1和-1来指定排序的方式,其中1为升序排列,-1为降序排列。

1
2
db.comm.find().sort({age:1})
查询comm所有文档并按照age升序排列

注意:

skip(),limit(),sort()三个放一起执行的时候,执行顺序是sort(),skip(),limit(),和命令的编写顺序无关。

复杂查询

正则的复杂条件查询

mongodb的模糊查询是通过正则表达式的方式实现的。其正则是用js的写法

1
2
3
4
5
db.comm.find(name:/zs/)
查询name中包含zs的文档

db.comm.find(name:/^zs/)
查询以zs开头的name的文档

name后的value不要加引号,这表示此为字符串,变成等于了

比较查询

image.png

包含查询

image.png

条件连接查询

image.png

索引

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

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

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

mongodb索引使用的是B tree 数据结构,MySQL是B+ tree

多路动态平衡树,简称b-tree,效率高,尤其是可以最大提高磁盘io效率,所以大部分数据库的存储引擎都用b-tree或者b-tree增强版作为索引的数据存储结构

image.png

image.png

image.png

索引的查看

返回一个集合中的所有索引的数组。

1
db.conn.getIndexes()

索引的创建

1
2
3
db.comm.createIndex({userid:1,age:-1},{unique:true})

创建一个以userid升序,age降序的复合唯一索引,选项options是可以省略的

索引的移除

1
2
3
4
5
db.comm.dropIndex("agess_1_age_-1")

删除索引名 agess_1_age_-1的索引,也可以通过索引规则删除
db.comm.dropIndex({userid:1})
删除仅以userid为升序的索引
1
2
3
db.comm.dropIndexes()

删除comm集合中的所有索引,但是mongodb自建的_id索引例外,它不会被删除

执行计划

1
db.comm.find(query,options).explain(options)

分析查询语句,查看是否用上了索引

image.png

设计模式

分桶设计

image.png

每分钟写入一次,如果运行一年则数据量极大,采用分桶模式进行分组,因为展现数据一般不会基于一分钟进行展现,最低级别可能都是一小时,因此将其合为一组即可极大的优化数据容量和读取速度。一般适用于数据点采集频繁,数据量过多的场景中。

image.png

image.png

列转行

image.png

导致创建新数据时要花费更多的数据,则可以采用列转行的方式,将相似的列变成一个列

image.png

版本字段

image.png

image.png

近似计算

在所需要的计算非常有挑战性或消耗的资源昂贵(时间、内存、CPU周期)时,如果精度不是首要考虑因素时,那么我们就可以使用近似值模式。

假设现在有一个相当规模的城市,大约有3.9万人。人口的确切数字是相当不稳定的,人们会搬入搬出、有婴儿会出生、有人会死亡。我们也许要花上整天的时间来得到每天确切的居民数量。在应用程序中,我们不需要每次更改都去更新数据库中的人口数。我们可以构建一个计数器,只在每达到100的时候才去更新数据库,这样只用原来1%的时间。在这个例子里,我们的写操作显著减少了99%。还有一种做法是创建一个返回随机数的函数。比如该函数返回一个0到100之间的数字,它在大约1%的时间会返回0。当这个条件满足时,我们就把计数器增加100。

我们为什么需要关心这个?当数据量很大或用户量很多时,对写操作性能的影响也会变得很明显。规模越大,影响也越大,而当数据有一定规模时,这通常是你最需要关心的。通过减少写操作以及不必要的“完美”,可以极大地提高性能。

预聚合字段

image.png

因为计算需要一行一行的扫描进行计算,因此可以采用预聚合模式

image.png

mongodb事物

写事物

image.png

image.png

1
2
db.conn.insert({count:1},{writeConcern:{w:"majority",wtimeout:3000}})
执行此写入操作时,要落入到大多数节点上后才算成功。如果在某个节点写入时间超时3S则直接返回写入失败。但是数据已经写入到其他成功的节点中了

image.png

读事物

readPreference (选择读哪个节点的数据)

image.png

默认是primary,并且这是推荐的做法,因为这样读肯定是读到最新的数据。nearest是根据ping time来决定。

image.png

image.png

image.png

readConcern (隔离级别)

image.png

image.png

image.png

1
2
db.conn.find().readConcern("local")
以local的隔离级别进行查询,如果是majority,大多数从节点未同步完,查询就会阻塞,直到大多数从节点同步完数据。

如果要使用majority级别必须要在配置文件中配置enableMajorityReadConcern=true (记住所有写操作的位置和状态)

image.png

多文档事物 (重要,平常代码中使用的事物)

image.png

image.png

image.png

image.png

image.png

image.png

Change Stream (触发器)

image.png

image.png

image.png

mongodb 代码编写规范

image.png

image.png

image.png

image.png

image.png

image.png

image.png

image.png

image.png

image.png

Licensed under CC BY-NC-SA 4.0