基础概念
MongoDB是一款为web应用程序和互联网基础设施设计的数据库管理系统,是NoSQL类型的数据库。
对比RDBMS
MongoDB提出的是文档、集合的概念,使用BSON(类JSON)作为其数据模型结构,其结构是面向对象的而不是二维表,存储一个用户在MongoDB中是这样子的。
{
username:'123',
password:'123'
}
使用这样的数据模型,使得MongoDB能在生产环境中提供高读写的能力,吞吐量较于关系型数据库大大增强。
易伸缩,自动故障转移。易伸缩指的是提供了
分片能力
,能对数据集进行分片,数据的存储压力分摊给多台服务器。自动故障转移是副本集
的概念,MongoDB能检测主节点是否存活,当失活时能自动提升从节点为主节点,达到故障转移。- 数据模型因为是面向对象的,所以可以表示丰富的、有层级的数据结构,比如博客系统中能把“评论”直接怼到“文章“的文档中,而不必像myqsl一样创建三张表来描述这样的关系。
主要特性
文档数据类型:
SQL类型的数据库是正规化的,可以通过主键或者外键的约束保证数据的完整性与唯一性,所以RDBMS常用于对数据完整性较高的系统。MongoDB在这一方面是不如SQL类型的数据库,且MongoDB没有固定的Schema,正因为MongoDB少了一些这样的约束条件,可以让数据的存储数据结构更灵活,存储速度更加快。schema(模式)是数据库的组织和结构,模式中包含了schema对象,可以是表、列、数据类型、视图、存储过程、关系、主键、外键等。
即时查询能力
MongoDB保留了关系型数据库即时查询的能力,保留了索引(底层是基于B tree)的能力。这一点汲取了关系型数据库的优点,相比于同类型的NoSQL redis 并没有上述的能力。
查阅平衡二叉树、B树、B+树数据结构概念
复制能力
MongoDB自身提供了副本集能将数据分布在多台机器上实现冗余,目的是可以提供自动故障转移、扩展读能力。
速度与持久性
MongoDB的驱动实现一个写入语义 fire and forget ,即通过驱动调用写入时,可以立即得到返回得到成功的结果(即使是报错),这样让写入的速度更加快,当然会有一定的不安全性,完全依赖网络。
MongoDB提供了Journaling日志的概念,实际上像mysql的bin-log日志,当需要插入的时候会先往日志里面写入记录,再完成实际的数据操作,这样如果出现停电,进程突然中断的情况,可以保障数据不会错误,可以通过修复功能读取Journaling日志进行修复。
数据扩展
MongoDB使用分片技术对数据进行扩展,MongoDB能自动分片、自动转移分片里面的数据块,让每一个服务器里面存储的数据都是一样大小。
Mongo服务模型
MongoDB核心服务器主要是通过mongod程序启动的,而且在启动时不需对MongoDB使用的内存进行配置,因为其设计哲学是内存管理最好是交给操作系统,缺少内存配置是MongoDB的设计亮点,另外,还可通过mongos路由服务器使用分片功能。
MongoDB的主要客户端是可以交互的js shell 通过mongo启动,使用js shell能使用js直接与MongoDB进行交流,像使用sql语句查询mysql数据一样使用js语法查询MongoDB的数据,另外还提供了各种语言的驱动包,方便各种语言的接入。
MongoDB结合键值存储和关系数据库的最好特性。因为简单,所以数据极快,而且相对容易伸缩还提供复杂查询机制的数据库。MongoDB需要跑在64位的服务器上面,且最好单独部署,因为是数据库,所以也需要对其进行热备、冷备处理。
MongoDB常见shell
mongodump
备份数据库,输出BSON格式数据
$ mongodump -h dbhost -d dbname -o dbdirectory
mongorestore
恢复数据库,源于mongodump
的数据
$ mongorestore -h <hostname><:port> -d dbname <path>
mongoexport
用来导出JSON、CSV和TSV数据。数据需要支持多格式时有用。mongoimport
还能用于大数据集的初始导入。
- -h,—host :代表远程连接的数据库地址,默认连接本地Mongo数据库;
- —port:代表远程连接的数据库的端口,默认连接的远程端口27017;
- -u,—username:代表连接远程数据库的账号,如果设置数据库的认证,需要指定用户账号;
- -p,—password:代表连接数据库的账号对应的密码;
- -d,—db:代表连接的数据库;
- -c,—collection:代表连接数据库中的集合;
- -f, —fields:代表集合中的字段,可以根据设置选择导出的字段;
- —type:代表导出输出的文件类型,包括csv和json文件;
- -o, —out:代表导出的文件名;
- -q, —query:代表查询条件;
- —skip:跳过指定数量的数据;
- —limit:读取指定数量的数据记录;
—sort:对数据进行排序,可以通过参数指定排序的字段,并使用 1 和 -1 来指定排序的方式,其中 1 为升序排列,而-1是用于降序排列,如sort({KEY:1})。
$ mongoexport -d dbname -c collection -f a,b,c --type=csv -o "<path>/test.csv"
mongoimport
用来导入JSON、CSV和TSV数据。数据需要支持多格式时有用。
-h,—host :代表远程连接的数据库地址,默认连接本地Mongo数据库;
- —port:代表远程连接的数据库的端口,默认连接的远程端口27017;
- -u,—username:代表连接远程数据库的账号,如果设置数据库的认证,需要指定用户账号;
- -p,—password:代表连接数据库的账号对应的密码;
- -d,—db:代表连接的数据库;
- -c,—collection:代表连接数据库中的集合;
- -f, —fields:代表导入集合中的字段;
- —type:代表导入的文件类型,包括csv和json,tsv文件,默认json格式;
- —file:导入的文件名称
- —headerline:导入csv文件时,指明第一行是列名,不需要导入;
$ mongoimport -d dbname -c collection -f a,b,c --type=csv --file "<path>/test.csv"
mongosniff
网络嗅探工具,用来观察发送到数据库的操作。基本上就是把网络上传输的BSON转换为易于人们阅读的shell语句切换数据库
创建数据库并不是必须的操作,数据库与集合只有在第一次插入文档时才会被创建,与对数据的动态处理方式是一致的。简化并加速开发过程,而且有利于动态分配命名空间。如果担心数据库或集合被意外创建,可以开启严格模式。严格模式: BSON类型的严格模式符合JSON RFC。任何JSON解析器都可以将这些严格模式的表示解析为键/值对;然而,仅有MongoDB内部JSON解析器可以识别由格式所表达的类型信息
use dba
插入
db.users.insert({username:"smith"})
db.users.save({username:"smith"})
- 区别: 若新增的数据中存在主键 ,insert() 会提示错误,而save() 则更改原来的内容为新内容。如:
已存在数据:{_id : 1, “ name “ : “ n1 “ },再次进行插入操作时,insert({_id : 1, “ name “ : “ n2 “ }) 会报主键重复的错误提示,save({ _id : 1, “ name “ : “ n2 “ }) 会把 n1 修改为 n2 。 - 相同点: 若新增的数据中没有主键时,会增加一条记录。已存在数据:{ _id : 1, “ name “ : “ n1 “ },再次进行插入操作时,insert({ “ name “ : “ n2 “ }) 插入的数据因为没有主键,所以会增加一条数据,save({ “ name “ : “ n2 “ }) 增加一条数据。
查找
db.users.find()
db.users.count()
更新
```javascript db.users.update({username:”smith”},{$set:{country:”Canada”}}) //把用户名为smith的用户的国家改成Canada
db.users.update({username:”smith”},{$unset:{country:1}}) //把用户名为smith的用户的国家字段给移除
db.users.update({username:”jones”},{$set:{favorites:{movies:[“casablance”,”rocky”]}}}) //这里主要体现多值修改,在favorties字段中添加多个值
db.users.update({“favorites.movies”:”casablance”},{$addToSet:{favorites.movies:”the maltese”}},false,true) //多项更新
<a name="PfLVn"></a>
## 删除
```javascript
db.foo.remove() //删除所有数据
db.foo.remove({favorties.cities:"cheyene"}) //根据条件进行删除
db.drop() //删除整个集合
索引
db.numbers.ensureIndex({num:1})
//创建一个升序索引
db.numbers.getIndexes()
//获取全部索引
基本管理
show dbs
//查询所有数据库
show collections
//显示所有表
db.stats()
//显示数据库状态信息
db.numbers.stats()
//显示集合表状态信息
db.shutdownServer()
//停止数据库
db.help()
//获取数据库操作命令
db.foo.help()
//获取表操作命令
Schema设计原则
MongoDB的自身特性
在关系型数据库中有带列和行的数据表。而MongoDB数据的基本单元是BSON文档,在键值中有指向不定类型值的键,MongoDB拥有即时查询,但不支持联结操作,简单的键值存储只能根据单个键来获取值,不支持事务,但支持多种原子更新操作。
关注系统本身的读写特性
读写比是怎样的,需要何种查询,数据是如何更新的,会不会存在什么并发问题,数据结构化的程度是要求高还是低。系统本身的需求决定mysql还是MongoDB。
设计模式
- 内嵌与引用 :当子对象总是出现在父对象的上下文中时,使用内嵌文档;否则将子对象单独存一个集合。
- 一对多的关系 :在“多”的集合关系中添加id指向依赖的id。
- 多对多 :在其中一种对应关系中使用对象数组指向另外一个对象。
- 树 :具化路径,在树中的每个节点都包含一个path字段,该字段具体保存了每个节点祖先的id。
- 动态属性 :可以为不同的动态属性添加索引,如果需要将属性圈在一个范围,那么可以通过key-value的方式,然后在统一的key上面加索引。
关于事务 :如果需要事务支持,那么只能选择另一种数据库,或者提供补偿性事务来解决事务的问题。
注意事项
不能创建没用的索引
- 不能在同一个字段中存不同的类型
- 不能把多类实体都放在一个集合里 不能创建体积大、嵌套深的文档
- 不能过多的创建集合,集合、索引、数据库的命名空间都是有限的
- 不能创建无法分片的集合
数据库的概念
数据库是集合的逻辑与物理分组,MongoDB没有提供创建数据库的语法,只有在插入集合时,数据库才开始建立。创建数据库后会在磁盘分配一组数据文件,所有集合、索引和数据库的其他元数据都保存在这些文件中,查阅数据库使用磁盘状态可通过。db.stats()
集合概念
集合是结构上或概念上相似得文档的容器,集合的名称可以包含数字、字母或 . 符号,但必须以字母或数字开头,完全。文档
其次是键值,在MongoDB里面所有的字符串都是UTF-8类型。数字类型包括double、int、long。日期类型都是UTC格式,所以在MongoDB里面看到的时间会比北京时间慢8小时。整个文档大小会限制在16m以内,因为这样可以防止创建难看的数据类型,且小文档可以提升性能,批量插入文档理想数字范围是10~200,大小不能超过16MB。索引与查询优化
索引的作用
- 索引能显著减少获取文档的所需工作量,具体的对比可以通过
.explain()
方法进行对比 - 解析查询时MongoDB通过最优计划选择一个索引进行查询,当没有最适合索引时,会先不同的使用各个索引进行查询,最终选出一个最优索引做查询
- 如果有一个a-b的复合索引,那么仅针对a的索引是冗余的
- 复合索引里的键的顺序是很重要的
索引类型
- 单键索引
- 复合索引
- 唯一性索引:索引列上增加了一层唯一约束。添加唯一性索引的数据列可以为空,但是只要存在数据值,就必须是唯一的。
- 稀疏索引:叶子节点仅保存了键位信息以及该行数据的地址,有的稀疏索引只保存了键位信息机器主键
索引的构建问题
如果数据集很大时,构建索引将会花费很长的时间,且会影响程序性能,可通过以下命令查看索引构建时间
当使用db.currentOp()
mongorestore
时会重新构建索引。当曾经执行过大规模的删除时,可使用
对索引进行压缩,重建。db.values.reIndex()
识别慢查询
- 查阅慢查询文件 ```javascript grep -E ‘([0-9])+ms’ mongod.log //使用grep 命令 识别命令信息
db.setProfillingLevel(2) //使用解刨器,将记录每次的读写到日志
db.setProfillingLevel(1) //只记录慢(100ms)操作
2. 分析慢查询
与 MySQL 的慢查询日志不同,MongoDB Profile 记录是直接存在系统 db 里的,记录位置 system.profile。所以,我们只要查询这个 Collection 的记录就可以获取到我们的 Profile 记录了。
查看最新的 Profile 记录:
```javascript
db.system.profile.find().sort({$natural:-1}).limit(1)
列出执行时间长于某一限度(5ms)的 Profile 记录
> db.system.profile.find( { millis : { $gt : 5 } } )
{ "ts" : ISODate("2012-05-20T16:50:36.321Z"), "info" : "query test.system.profile reslen:1219 nscanned:8 \nquery: { query: {}, orderby: { $natural: -1.0 } } nreturned:8 bytes:1203", "millis": 0 }
- ts: 该命令在何时执行
- info: 本命令的详细信息
- reslen: 返回结果集的大小
- nscanned: 本次查询扫描的记录数
- nreturned: 本次查询实际返回的结果集
-
副本集
副本集可以提供主从复制能力、热备能力、故障转移能力,增强了集群的强壮性和查询能力
构建方式
rs.initiate()
rs.add("localhost:40001")
rs.add("localhost:40002",{arbiterOnly:true})
监控
db.isMaster()
rs.status()
副本集的工作原理
实际上MongoDB对副本集的操作跟mysql主从操作是差不多的
mysql: 主binlog -> 从relay.log -> 从bin.log -> 从数据库
- mongodb: 主oplog -> 从oplog
写操作先被记录下来,添加到主节点的oplog里。与此同时,所有从结点复制oplog。首先,查看自己oplog里最后一条的时间戳;其次,查询主节点oplog里所有大于此时间戳的条目;最后,把那些条目添加到自己的oplog里并应用到自己的库里。从节点使用长轮询立即应用来自主结点oplog的新条目。
当遇到以下情况,从节点会停止复制
- 如果从节点在主节点的oplog里找不到它所同步的点,那么会永久停止复制
- 一旦某个从节点没能 在主节点的oplog里找到它已经同步的点,就无法再保证这个从结点的完美副本
local数据库保存了所有副本集元数据和oplog日志
- replset.minvalid:包含指定副本集成员的初始化同步信息
- system.replset:保存在副本集配置文档
- system.indexes:标准索引说明容器
- me slaves:主要用于写关注
可以使用以下命令查看复制情况
db.oplog.rs.findOne()
心跳检测
每个副本集成员每秒钟ping一次其他所有成员,可以通过rs.status()看到节点上次的心跳检测时间戳和健康状况。
故障转移
如果从节点和仲裁节点都被杀了,只剩下主节点,他会把自己降级成为从节点。
提交和回滚
如果主节点的数据还没有写到从库,那么数据不能算提交,当该主节点变成从节点时,便会触发回滚,那些没写到从库的数据将会被删除,可以通过rollback子目录中的BSON文件恢复回滚的内容。
驱动和复制
- 使用单节点链接
只能链接到主节点,如果链接到从节点的话,会被拒绝写入操作,但是如果没有使用安全模式,因为mongo的fire and forget 特性,会把拒绝写入的异常给吃掉。
- 使用副本集方式链接
能根据写入的情况自动进行故障转移,但是当副本集进行新的选举时,还是会出现故障,如果不使用安全模式,依旧会出现写不进去,但现实成功的情况。
- 写关注
可以使用写关注来关注数据是否已经被写入MongoDB的库中,使用写关注会消耗性能,需要在速度和持久性之间做出权衡。
分片
当数据量过大,索引和工作数据集占用的内存就会越来越多,所以需要通过分片负载来解决这个问题
工作原理
分片组件
- 分片:每个分片都是一个副本集
- mongos路由器:是一个路由器,将读写请求指引到合适的分片上
配置服务器config:持久化分片集群的元数据,包括:
分片一个集合:分片是根据一个属性的范围进行划分的,MongoDB使用所谓的分片键让每个文档在这些范围里找到自己的位置
块:是位于一个分片中的一段连续的分片键范围,可以理解为若干个块组成分片,分片组成MongoDB的全部数据
拆分和迁移
块的拆分:初始化时只有一个块,达到最大块尺寸64MB或100000个文档就会触发块的拆分。把原来的范围一分为二,这样就有了两个块,每个块都有相同数量的文档。
迁移:当分片中的数据大小不一时会产生迁移的动作,比如分片A的数据比较多,会将分片A里面的一些块转移到分片B里面去。分片集群通过在分片中移动块来实现均衡,是由名为均衡器的软件进程管理的,任务是确保数据在各个分片中保持均匀分布,当集群中拥有块最多的分片与拥有块最少分片的块差大于8时,均衡器就会发起一次均衡处理。
操作
sh.help() //查看分片相关帮助
sh.addShard() //添加分片
db,getSiblingDB("config").shards.find() //查看分片列表
sh.status() //分片详情
sh.enableSharding("cloud-docs") //开启一个数据库上的分片
db.getSiblingDB("config").databases,find() //查看数据库列表
sh.shardCollection("cloud-docs.spreadsheets",{username:1,_id:1}) //使用一个分片键定义一个分片集合spreadsheets,根据用户名进行切分
sh.getSiiblingDB("config").collections.findOne() //查看集合列表
db.chunks.count() //查看块的个数
db.chunks.findOne() //查看块的信息
db.changelog.count(}what:"split"|) //查看块切分日志
db.changelog.find({what:"moveChunk.commit"}).count() //查看日志迁移记录
查询与索引
分片查询类型
针对性查询:查询包含分片键
- 全局查询或分散/聚集查:查询不包含分片键
- 查询过程:通过分片键将查询路由给指定分片,一旦到了某个分片上,由分片自行决定使用哪个索引来执行该查询
索引
每个分片都维护了自己的索引,当在分片集合上声明索引时,每个分片都会为它那部分集合构建独立的索引,每个分片上的分片集合都应该拥有相同的索引。
分片集合只允许在_id字段和分片键上添加唯一性索引,其他地方不行,因为这需要在分片间进行通信,实施起来很复杂。
当创建分片时,会根据分片键创建一个索引。选择分片键
- 分片键是不可修改的、分片键的选择非常重要
- 低效的分片键
- 分布性差:如使用BSON对象ID,那么会导致所有最新插入的文档都会落到某个很小的连续范围,无法分散插入
- 缺乏局部性:升序分片键有明确的方向,完全随机的分片键则根本没有方向。前者无法分散插入,后者插入分散,如使用MD5作为分片键
- 理想的分片键
- 将插入数据均匀分布到各个分片上
- 保证CRUD操作能够利用局部性 有足够的粒度进行块拆分
满足这些要求的分片键通常由两个字段组成,第一个是粗粒度的,第二个粒度较细
生产环境中的分片
部署拓扑
复制mongod:需要独立的部署服务器
- 配置服务器:配置服务器不需要有自己的机器
最低要求
- 副本集每个成员,无论是完整的副本集节点还是仲裁节点,都需要放在不同的机器上 每个用于复制的副本集成员都需要有自己的机器
- 副本集仲裁节点很轻量级,和其他进程共用一台机器即可
- 配置服务器也可以选择与其他进程共用一台机器
注意事项
需要估计集群大小,可使用以下命令对现有集合进行分片处理
sh.splitAt("cloud-docs.spreadsheets",{"username":"chen","_id":ObjectId("")})
//手动拆分块
sh.moveChunk("cloud-docs.spreadsheets",{username:"chen"},"shardB")
//手动将某分块移至分片B
db.runCommand({removeshard:"shard-1/arete:30100,arete:30101"})
//删除分片
db.runCommand({moveprimary:"test",to:"shard-0-test-rs"});
//移动主分片
备份分片集群
备份分片时需要停止均衡器
db.settings.update({_id:"ba;ancer"},{$set:{stopped:true},true});
sh.setBalancerState(false);
//停止均衡器,此时均衡器将进行最后一轮均衡
db.locks.find({_id:"balancer"});
sh.isBalancerRunning();
//查看均衡器状态,任何状态大于0 的状态值都说明均衡器仍在进行中
部署和管理
使用64位机器、32位机器会制约mongodb的内存,使其最大值为1.5GB
CPU
mongodb 只有当索引和工作集都可放入内存时,才会遇到CPU瓶颈,CPU在mongodb使用中的作用是用来检索数据,如果看到CPU使用饱和的情况,可以通过查询慢查询日志,排查是不是查询的问题导致的,如果是可以通过添加索引来解决问题
mongodb写入数据时会使用到CPU,但是mongodb写入时间一次只用到一个核,如果有频繁的写入行为,可以通过分片来解决这个问题
内存
大内存是mongodb的保障,如果工作集大小超过内存,将会导致性能下降,因为这将会增加数据加载入内存的动作
硬盘
mongodb默认每60s会与磁盘强制同步一次,称为后台刷新,会产生I/O操作。在重启时mongodb会将磁盘里面的数据加载至内存,高速磁盘将会减少同步的时间
文件系统
文件描述符
linux 默认文件描述符是1024,需要大额度的提升这个额度。文件描述符不足可能会导致读取文件失败
文件描述符:简称fd,当应用程序请求内核打开/新建一个文件时,内核会返回一个文件描述符用于对应这个打开/新建的文件,其fd本质上就是一个非负整数,读写文件也是需要使用这个文件描述符来指定待读写的文件的