MongoDB权威指南笔记
##< MongoDB权威指南>笔记
MongoDB基础知识
-
文档(行)-》 集合(动态模式的表,集合可以有子集合(GridFS))-》数据库
-
每个文档有个特殊的键_id (唯一生成方式,时间戳+机器ID+PID+计数器)
- 命名
- 集合system保留,注意有些保留字没有强制限定,比如version,就只能用getCollection来访问了,或者跳过直接访问,使用数组迭代语法
-
数据库,admin local config 保留
- JavaScriptShell操作 use db CRUD
基本数据结构 ALL IN JSON
-
null 二进制以及代码
-
bool 数值,默认64位浮点型,可以用NumberInt类NumberLong类
- 字符串,日期(new Date() 直接调用Date构造函数得到的是个字符串,所以这里用new,标识Date 对象,而不是对象生成的字符串)
-
正则 /foobar/i js正则语法(需要学一下js)
- 数组,数组可以包含不同类型的元素,数组内容可以建索引,查询以及更新
- 内嵌文档(嵌套Json,也可以建索引,查询以及更新)
- 对象ID
- _id默认是ObjectID对象,每个文档都有唯一的 _id ObjectId全局唯一的原理
- 自动生成id 通常能交给客户端驱动程序搞定就不放在服务器,扩展应用层比扩展数据库层要方便
JS Shell
- .mongorc.js文件,放在bin下自动运行,可以覆盖危险Shell辅助函数,取消定义,也可以启动Shell时 –norc 取消加载
- Windows用户,该文件会默认生成在用户 用户名或administrator目录下
创建,更新和删除文档
插入文档
-
insert, batchinsert 批量插入,多个文档插入到同一个集合中,多个文档插入多个集合不行。
- 导入原始数据使用mongoimport (需要学一下go)
- 插入最大接受48M插入,多于48M会分片插入,如果插入途中有一个文档插入失败,前面的灰尘共,后面的都失败,不是强事务的。
- 插入校验,添加_id字段,检查大小(目前是小于16M)(可能是不良的设计)
删除
- db.foo.remove() 删除集合的所有文档删除数据是永久的,不能撤销,也不能恢复
- drop删除整个表(和数据库一样,快,没有限定条件)
更新文档
-
文档替换,直接更改文档的字段,扩展,重命名,建立子文档等 可以用=,直接用delete删除字段(只能用替换,不能直接删数据库,用findOne,find返回值是个cursor)
-
文档更新相同字段导致的更新错误(不唯一,有可能给id在前的更新了),索引问题,用id来查找
-
修改器,一个Json封起来。key是动作,value是个修改的Json,其中,内部key对应修改的字段,value对应修改的值
-
$set == replace or insert, $unset 直接就可以删掉这个键 可以修改内嵌文档
-
$inc == add or insert
-
数组修改器
- $push 添加元素 添加一个数组元素,$push 和$each结合使用,添加多个数组
-
数组作为数据集,保证数据不重复 $ne一个限定集,或使用$addToSet($addToSet和$each可以结合使用)
- 删除元素 {“$pop”:{“key”:-1}}基于位置,如果基于条件,使用$pull
-
基于位置的数组修改器 直接使用定位符$来匹配,匹配第一个 db.blog.update({“cmt.author”:”John”},{“$set”:{“cmt.$.author”:”Jim”}})
- 修改器速度, 主要取决于文档大小是否变化,如果有变化,就会影响速度,因为插入是相邻的,如果一个文档变大,位置就放不下,就会有移动
-
paddingFactor,填充因子,MongoDB位每个新文档预留的增长空间,(db.coll.stats(),Windows上我运行这个没找到paddingFactor项,比较尴尬)
- upsert update + insert Update第三个参数true 原子性
- $setOnInsert 考虑寻找并修改同一个字段,多次运行可能会生成多个文档,setOnInsert会找到之前生成的文档,不会多次生成。
-
save shell (db.foo.save(x))在写到这行之前我都是先findOne查回来,改,在Update回去。。。
- 更新多个文档 db.user.update({“birthday”:”10/13/1978”},{““$set”:{“gift”:”Happy Birthday”}},faset,true)
-
db.runCommand({getLaseError:1}) 查看更新文档数量
- 返回被更新的文档 findAndModify ?有点没理解
写入安全
查询
- find 默认匹配全部文档{},也可以写多个条件(and关系)
- 指定返回,第二个参数指定{“key”:1/0},类似select指定,其中1是选中,0是排除
-
限制 ?有点没理解
- 查询条件 “$lt” “$lte” “$gt” “$gte” “$ne”
- OR “$in” “$or “ “$not” “$nin”
- 条件语义
特定类型的查询
- null
- 正则表达式
- 查询数组 与查询普通文档是一样的,直接find value能匹配到数组中的元素,就会被选出来
- $all 特定元素的并列条件,顺序无关紧要,普通find子数组的形式不行,会有顺序限制,如果想找特定位置的元素,需要使用key.index (上面的 key.$就是个迭代形式。)
- $size 指定长度的数组
- $slice 返回一个子集 截取一段
- 数组和范围查询的相互作用,因为查找数组会匹配所有元素,有满足的就会被选出来,所以范围查询可能会和设想的结果不一样
- 针对数组,使用 $elemMatch 效率稍低
-
如果该列有索引,直接用min max
- 查询内嵌文档 用.访问 (URL作为键的弊端)
- $where 结合函数 会很慢,用不了索引,还有文档转换为js(配合where函数)的开销
-
注意可能引入的注入攻击
-
游标 find
- while(cursor.hasNext()){obj=cursor.next(),do….}
- cursor.forEach(function(x){…})
- limit skip sort 类似SQL中的limit sort
- skip不要过滤大量结果,会慢
- 不要用skip对结果分页
- 注意比较顺序
- 随机选取文档,不要算出所有然后在找随机数,太坑了。可以每个文档加一个随机数key,在key上建索引,然后用随机随机数过滤就ok
-
也有可能找不到结果,那就反向找一下,还没有那就是空的
- 高级查询选项
- 获取一致结果:迭代器修改文档可能引入的问题,修改的文档大小发生变化,结果只能放在文档最后,导致同一个文档返回多次(太坑了)
-
解决方案,snapshot,会使查询变慢,只在必要的时候使用快照,mongodump
-
游标的生命周期 默认十分钟自动销毁,有immortal选项
- 数据库命令 runCommand 实际上内置命令是db.$cmd.findOne({“”})的语法糖
设计应用
索引
- 和关系型数据库类似,也有explain
- 复合索引 db.coll.ensureIndex({“key1:1”,”key2:!”})
- 覆盖索引与隐式索引
- $如何使用索引
- 低效率的操作符,基本都不能用索引
- 范围 要考虑与索引结合导致的效率低下问题
-
$or使用索引
- 索引对象和数组
- 可以嵌套对象的任意层级来做索引 a.b.c.d.e.f ,得用最后级别的对象来搜索才能用上这个索引,只用其中一个字段是不行的。
- 索引数组 实际上是对每一个数组元素都建立了索引,代价很大,如果有更新操作,那就完了,不过这里可以对字段来查找,毕竟每个字段都有索引。但是索引不包含位置信息。没法针对特定位置来找。
-
限定只有一个数组可以索引。为了避免多键索引中索引条目爆炸
- 多键索引 多键索引可能会有多个索引条目指向同一个文档 ?为什么 会很慢,mongo要先去重
索引基数
-
基数 衡量复杂度,某个字段拥有不同值的数量,基数越高索引越有效,索引能将搜索范围缩小到一个小的结果集
-
查询优化器
何时不应该使用索引
- 索引扫描和全盘扫描在集合与返回结果上进行比较
索引类型
-
唯一索引 和主键概念一致(虽然已经有_id 这个主键了,新增的唯一索引是可以删除的)db.coll.ensureindex({“ukey”:1},{“unique”:true})
-
复合唯一索引。一例 GridFS files_id:ObjectId, n:1 所有索引键的组合必须唯一
-
去处重复,建唯一索引失败 ,加个条件ensureindex({“ukey”:1},{“unique”:true,”dropDups”:true})
( 慎用) - 稀疏索引 和关系型数据库中的索引不同?
-
有些文档可能没有索引这个字段,建立稀疏索引就把这种过滤掉了。
- 索引管理 system.indexes db.coll.getIndexes()
- 标识索引 keyname1_dir1_keyname2_dir2….keynameN_dirN 名字,方向
- 修改索引 db.coll.dropIndex(“indexname”)
特殊的索引和集合
固定集合capped collection
-
事先创建好,大小固定,类似循环队列 在机械硬盘写入速度很快
顺序写入,没有随机访问。如果拥有专属磁盘,没有其他集合的写开销,更快 - db.createCollection(“my_coll”,{“capped”:true,”size”:100000,”max”:100}); 也可以用普通的集合转成固定集合 db.runCommand(“convertToCapped”:”test”,”size”:100000),无法将固定集合改成非固定集合,只能删掉
- 自然排序
-
循环游标 tailable cursor 判断cursor是否tailable 一直循环处理,直到cursor死了
(灵感来自tail命令) - 没有_id索引的集合,在插入上带来速度提上,但是不符合mongod复制策略
TTL索引
- 创建索引有个expireAfterSecs参数 db.foo.ensureIndex({}”lastUpdated”:1},{“expireAfterSecs”:60*60*24})
全文本索引
-
自然语言处理?
-
优化的全文本索引
-
在其他语言中搜索,设定语言
地理空间索引2dsphere
- 复合地理空间索引
- 2D索引
使用GridFS存储文件
- 使用场景,不经常改变的需要连续访问的大文件,缺点性能低,文件是多个文档,无法同一时间对所有文档加锁
- mongofiles put foo.txt get foo.txt
聚合
聚合框架
- 管道pipeline 投射project 过滤filter 分组group 排序sort限制limit跳过skip,都在内存中进行,顺序不影响结果
(但可能影响性能)
db.articles.aggregate({"$project":{"author":1}},
{"$group":{"_id":"author","count":{"$sum":1}}},
{"$sort":{"$count":-1}},
{"$limit":5})
$match 最开始用可以用上索引减小结果集
$project 修改字段前先用上索引
-
管道表达式 数学表达式 $add $ subtract $multiply $divide $mod 都是接一个数组对象的
-
日期表达式 $year $month $week $dayOfMonth $dayOfWeek $dayOfYear $hour $minute $second
-
字符串表达式 $substr $concat $toLower:expr $toUpper:expr
-
逻辑表达式
-
$cmp $strcasecmp $eq ne ge get lt lte and or not
-
$cond :[boolexpr,trueexpr, falseexpr] $ifNull:[expr,replacementexpr]
$group 分组 SQL - groupby
- 算术操作符 $sum $average:value
- 极值操作符 $max:expr $min:expr $first:expr $last:expr
- 数组操作符 $addToSet $push
- 分组行为不能用于流式工作,有个归结的过程,是个整体
- $unwind 拆分成独立的文档
- $sort limit skip
- 使用管道开始阶段前尽可能把多于的文档和字段过滤掉,可以排序来方便使用索引 $match
mapreduce?
聚合命令 和上面的不一样,但是感觉差不多。
- db.col.count()
- runCommand({“distinct”:”key1”,”…”}
- runCommand({“group”:} SQL group by
应用程序设计
注意一致性,以及mongodb不支持事务
复制
创建副本集 replica set
副本集概念 大多数,半数以上,低于半数,全部降备
选举机制
选举仲裁者 最多一个 arbiter 只参与选举,放在外部观察的故障域中,与其他成员分开,尽量不要用,虽然轻量,极端场景 1主1备1仲裁,主挂掉,备升主,新主机还要承担起复制备机的责任
尽量使用奇数个数据成员
优先级与被动成员
隐藏成员?为啥有这个设定
延迟备份节点,主节点意外被毁被删库还能活过来 不支持回滚只好这么搞
备份节点可以手动设定不创建索引(得是被动成员,优先级为0)
副本集的组合
同步 操作日志oplog,主节点local数据库的一个固定集合,备份节点查这个集合来进行复制,每个节点维护自己的oplog,每个成员都可以作为同步源提供给其他成员使用
- oplog同一条记录执行一次和执行多次效果一致
初始化同步 会先将现有的数据删除 克隆 通过oplog复制数据,然后复制oplog 然后建索引
- 如果当前节点远远落后于同步源,oplog通不过城最后一步就是将创建索引期间的所有操作全同步过来,防止该成员成为备份节点
- 克隆可能损坏同步元的工作集?
- 克隆或创建索引耗时过长,导致新成员与同步源oplog脱节 初始化同步无法正常进行
处理陈旧数据 stale 会从成员中的oplog(足够详尽)中同步恢复,如果都没有参考价值,这个成员的复制操作就会停止,这个成员需要重新进行完全同步
- 心跳 让主节点知道自己是否满足集合的大多数条件
- 成员状态 主节点备份节点
- STARTUP成员刚启动 ->STARTUP2初始化同步 ->RECOVERING 成员运转正常,但暂时还不能处理读取请求 比如成为备份节点前的准备活动,在处理非常耗时的操作(压缩,replSetMaintenance),成员脱节
- ARBITER 仲裁者
- DOWN变的不可达 UNKNOWN 无法到达其他成员 分别描述对端和自己
- REMOVED移除工作集
- ROLLBACK 数据回滚
-
FATAL发生了不可挽回的错误 grep replSet FATAL 通常只能重启服务器
- 选举 选举通常很快,太多错误发生比较倒霉的话可能花费较长
- 回滚
- 一个场景,假如主节点op写写死了,然后选举新的主节点没有这条记录,则原主节点需要回滚
- 如果回滚失败,备分节点差的太多,升主回滚内容太多,mongodb受不了。
从应用程序连接副本集
主节点崩溃的错误?最后一次操作成功失败?留给应用程序去查询
-
db.runCommand({“getLastError”:1,”w”:majority})检查是否写入操作被同步到了副本集的大多数 阻塞,可以设置超时时间,阻塞不一定失败,超时不一定失败
- 使用majority选项可以避免一场写入失败导致的回滚丢失。一直阻塞直到大家都有这条数据,或失败。
- “w”也可以设定其他值,值包含主节点。应该设置成n+1
默认1
自定义复制保证规则
- 保证复制到每个数据中心的一台服务器上
- 重写rs.config
,rs.reconfig(config)添加字段 - 隐藏节点到底是干啥的?
将读请求发送到备份节点
- 对一致性要求不是特别的高
- 分布式负载
(另一个选择,分片分布式负载) - 何时可以从备份节点读取数据?
- 失去主节点时,应用程序进入只读状态-》主节点优先
管理
维护独立的成员
- 单机模式启动成员 改端口,重启时不使用replSet选项,然后访问维护,其他成员会连接失败,认为它挂了,维护结束后重新以原有的参数启动,自动连接原来的副本集,进行同步
副本集设置
local.system.replSet
var config = {"_id":setname,
"members":["_id":0,"host":host1,
"_id":1,"host":host2,
"_id":2,"host":host3
]}
re.initiate(config)
修改副本集成员rs.add remove reconfig
修改成员状态 re.stepDown降备rs.freeze (time)在time时间内阻止升主
使用维护模式 RECOVERING replSetManitenanceMode
监控复制
获取状态 replSetGetStatus
复制图谱,指定复制syncFrom
- 复制循环
- 禁用复制链,强制从主节点复制
计算延迟 lag
调整oplog大小 维护工作的时间窗,超过改时间就不得不重新进行完全同步
-
在被填满之前,没有办法确认大小
,即使他是个capped collection,也不可以运行时调整大小,因为他是个capped collection -
修改步骤,如果是主节点,先退位,关闭服务器,单机模式启动,临时将oplog中最后一条insert操作保存到其他集合中 确认保存成功 删除当前oplog,创建一个新的oplog,将最后一条操作记录写回oplog确保写入成功
var cursor = db.oplog.rs.find({"op":i})
var lastInser = cusor.sort({"$narutal":-1}).limit(1).next()
db.tempLastOp.save(lastInsert)
db.tempLastOp.findOne()
db.oplog.rs.drop()
db.createCollection("oplog.rs",{"capped":1,"size":10000})
var temp = db.tempLastOp.findOne()
db.oplog.rs.insert(temp)
db.oplog.rs.findOne()
从延迟备份节点中恢复
- 简单粗暴方法,关闭所有其他成员,删掉其他成员的数据,重启所有成员,数据量大可能会过载
- 稍作修改,关闭所有成员,删掉其他成员的数据,把延迟备份节点数据文件复制到其他数据目录,重启所有成员,所有服务器都与延迟被分界点拥有同样大小的oplog
创建索引
创建索引消耗巨大,成员不可用,避免最糟糕的情况,所有成员节点同时创建索引
- 可以每次只在一个成员创建索引,分别备份节点单机启动创建索引然后重新启动,然后主节点创建索引
- 可以直接创建,选择负载较少的空闲期间
- 修改读取首选项,在创建节点期间把读分散在备份节点上
-
主节点创建索引后,备份节点仍然会复制这个操作,但是由于备份节点中已经有同样的索引,实际上不会再次创建索引
- 让主节点退化为备份节点,执行上面的步骤,这时就会发生故障转移
- 可以使用这个技术为某个备份节点创建与其他成员不同的索引,在离线数据处理时非常有用?
- 但是如果某个备份节点的索引与其他成员不同,它永远不会成为主节点,应该将它的优先级设为0
- 如果要创建唯一的索引,需要确保主节点中没有被插入重复的数据,或者首先为主节点穿件唯一索引,否则主节点重复数据,备份节点复制出错,备份节点下线,你不得不单机启动,删除索引,重新加入副本集
在预算有限的情况下进行复制
没有多台高性能服务器,考虑将备份节点只用于灾难恢复
- “priority”:0 优先级0, 永远不会成为主节点
- “hidden”:1 隐藏,客户端无法将读请求发送给他
- “buildIndexes”:0 optional 备份节点创建索引的话开极大地降低备份节点的性能。如果不在备份节点上创建索引,从备份节点上恢复数据需要重新创建索引
- “votes”:0 只有两台服务器的情况下,备份节点挂掉后,主节点仍然一直会是主节点,不会因为达不到大多数要求而推诿,如果还有第三台服务器,设为仲裁者
主节点如何跟踪延迟
local.me local.slaves
##### 主从模式 传统模式,会被废弃
从主从模式切换到副本集模式
- 停止所有系统的写操作哦,关闭所有mongod 使用–replSet选项重启主节点不再使用–master 初始化这个只有一个成员的副本集,这个成员会成为副本集中的主节点,使用–replSet和–fastsync启动从节点,使用rs.add将之前的从节点添加到副本集,然后去掉fastsync
让副本集模仿主从模式行为
- 除了主节点,都是优先级0投票0 备份节点挂了无所谓,主节点不会退位,主节点下线手动选父节点,指定优先级和投票
,运行rs.reconfig(config,{“force”,1})
分片
配置分片 数据分片
拆分块
- 有服务器不可达,尝试拆分,拆分失败 拆分风暴
- mongos频繁重启,,重新记录点,永远达不到阈值点
均衡器 config.locks
均衡阈值
选择片键
如何在多个可用的片键中作出选择?不同使用场景中的片键选择?哪些键不能作为片键?自定义数据分发的可选策略?如何手动对数据分片
分片的目的
- 减少读写延迟?将请求分布在近的机器或高性能服务器
- 增大读写吞吐量?集群1000次/20 ms 可能需要5000次/20ms,需要提高并行,保证请求均匀分布在各个集群成员上
- 增加系统资源?每GB数据提供Mongodb更多的可用RAM,使工作集尽量小
数据分发
-
升序片键 date ObjectId
- 随机分发片键
- 基于位置片键 tag
片键策略
- 散列片键 数据加载速度
- 缺点 无法范围查询
- 无法使用unique和数组字段
-
浮点型的值会被先取整,然后算hash
- GridFStab的散列片键
- 流水策略,一个SSD接着写,指定tag,更新tag范围,把片转到其他磁盘上
- 多热点?
片键规则和指导方针
- 不能是数组
- 片键的势要高一些,或者组合起来
##### 控制数据分发
-
addShardTag第二个参数可以指定高低,然后tagrange将不同集合放到不同的分片上。
-
手动分片 moveChunk
分片管理
检查集群状态 sh.status() use config config.shards config.chunks config.collections cofig.changelog
应用管理
了解应用的动态
mongotop
mongostat
-
insert/query/update/delete/getmore/command
- fulshed mongod将数据刷新到磁盘的次数
- mapped所映射的内存大小,约等于数据目录大小
- vsize 虚拟内存大小,通常为数据目录二倍
- res正在使用的内存大小
- locked db锁定时间最长的数据库
- qrw 阻塞的读写队列大小
- arw正在读写的客户端数量
- netin 网络传输进来的字节数
- netOut网络传输输出的字节数
- conn打开的连接数
数据管理
身份认证
建立和删除索引
OOM Killer 日志在/var/log/messages
数据预热
for file in /data/db/* do
dd if=$file of=/dev/null
done
-
fine-grained 细粒度的预热
-
将集合移动至内存
db.runCommand({"touch":"logs","data":1,"index":1})
- 加载特定的索引 覆盖查询
- 加载最新的文档
- 加载最近创建的文档 结合ObjectId
- 重放应用使用记录 诊断日志?
压缩数据
-
会进入RECOVERING模式
-
运行repair来回收磁盘空间,需要有当前数据大小同样的空闲空间,或指定外部的磁盘目录,修复路径。
移动集合
- renameCollection 改集合名。不用担心性能消耗
- 数据库间移动,只能dump&restore也可以用cloneCollection
预分配数据文件
if test $# -lt 2 || test $# -gt 3 then
echo "$0 <db> <number-of-files> "
fi
db =$1
num=$2
for i in {0..$num}
do
echo "preallocation %db.$i"
head -c 2146435072 /dev/zero >$db.$i
done
持久性
日记系统
批量提交写入操作 100ms 或写入数据MB以上
设定提交时间间隔
关闭日记系统 数据可能损坏,两种替代方案
- 替换数据文件,用副本集的,或者初始化同步,重启同步
- 修复数据文件,mongod内置(repair)或mongodump 耗时较长
关于mongod.lock文件,别手动删。。恢复数据再搞。也有可能是隐蔽的异常退出
MongoDB无法保证的事项
- 检验数据损坏
- 副本集的持久性
服务器管理
停止和启动MongoDB
监控MongoDB
- 内存使用情况
- 跟踪监测缺页中断
- 索引树的脱靶次数
- IO延迟
- 后台刷新平均时间,将脏页写入磁盘所花费的时间
- 跟踪监测性能状况
- 空余空间
- 监控副本集
- oplog长度
备份
- 文件系统快照,开启日记系统,文件系统本身支持快照
- 复制数据文件,先db.fsyncLock() 然后cp就完了
- 不要同时使用fsyncLock和mongodump 会死锁
部署