MongoDB权威指南--笔记

时间:2023-03-08 16:21:44
MongoDB权威指南--笔记

mongodb并不具备一些在关系型数据库中很普遍的功能,如连接和复杂的多行事务。

集合-->文档-->id id在文档所属的集合中是唯一的。

db.help()查看数据库级别的帮助,db.foo.help()查看集合级别的帮助。

db.foo.update --不加(),查看函数具体功能,使用参数。

运行脚本:
mongo --quite host:port/foo scripts.js 连接到指定服务器的foo数据库执行脚本scripts.js文件。

load()函数从交互模式运行脚本:load("scripts.js")

db.version() --返回服务器的版本信息
db.getCollection("version") --访问version数据库:访问无效集合(只能包含字母、数字、$、_,且不以数字开头)

x.y=x['y'] --使用此方法访问无效属性的集合
var name="#$@!"
db[name].find()

var collections = ["cneter","account","chat"]
for (var i in collections){
print(db.blog[collections[i]]);
}

插入:
db.center,insert({"test":1})
批量插入:
db.center.batchInsert({"test":1},{"test":2},{"test":3})
--将多个文档插入一个集合,不能在一次请求中将多个文档插入到多个集合中。

驱动程序允许批量插入的文档有限制,在失败之前的所欲数据都成功插入,之后的数据全部丢失。
使用collecyionOnError选项忽略错误,并继续执行后续插入操作。
shell不支持collectionOnError选项,所有驱动程序支持。

mongodb只进行最基本的检查,插入非法数据很容易,因此,应该只允许信任的源(应用程序服务器)连接数据库。

删除:
db.center.remove() --删除所有的文档
db.center.remove({"test":1}) --删除指定条件的数据
所有的删除都是永久性的,不可以撤销、恢复
删除效率:db.center.remove()<db.center.drop()

"$set":指定一个字段的值,如果字段不存在,就创建。
db.cneter.update({"_id":1234},{"$set":{"favorite book":"Hao ma,Hao de"}})
--增加favorite book字段,修改字段均可使用"$set"。
db.center.update({"_id":1234},{"$unset":{"favorite":1}}) --将添加的字段删除
使用键值对进行替换,会将整个文档以新的键值对覆盖掉,需要使用¥修饰器来修改键值对.

"$inc":增加已有键的值。
db.center.update({"name":"kasumi","$inc":{"score":50}}) --值为50
db.center.update({"name":"kasumi","$inc":{"score":100}}) --值为150
仅修改数值类型

"$push":向数组末尾加入一个元素。
"$slice":限制数组所包含的最大长度.
"$sort":继续排序. --"$slice"和"$sort"需要和"$push"组合使用,且必须使用"$each".

"$addToSet":避免插入时,数据重复。
db.center.update({"_id":1234},"$addToSet":{"email":{"$each":["kasum@qq.com","kasumi@qq.com"]}})

删除:
1)把数组看成队列或栈,"$pop" --{"$pop":{"key":1}}:从数组末尾删除一个元素.
2)基于特定的条件删除元素:"$pull" --会删除所有的匹配文档,而不是只删除一个.

对集合中间的文档进行修改,若使得文档尺寸变大,这个文档将会被移动到集合的尾部。
移动文档后,会修改集合中的填充因子,当文档尺寸增大,移动到最尾部的时候,它原来所占有的位置就闲置了。
在集合中插入新的文档,会产生新的填充因子,若不产生位置的移动时,填充因子的大小会逐渐减小。
--尽量设置填充因子的值接近1.
频繁移动文档会产生大量空的数据文件,如果有太多不能重用的空白空间 --Error:was empty,skipping ahead
db.runcommand({"collMod":collectionName,"usePowerOf2Sizes":true}) --提高磁盘复用率.

默认情况下,只更新满足条件的第一个文档,需要更改满足条件的所有文档,需要将uodate的第四个参数设置为true.

db.runCommand({getLastError:1}) --查询更新的文档的数量.
findAndModify --返回修改的数据,进行重新定义.
db.runCommand({"findAndModify":"center","query":{"status":"ready"},"update":{"$set":{"status":"running"}}})

查询:
find 空查询文档({}):匹配集合的全部内容.不指定查询条件,默认是{}。

指定需要返回的键:find或者findone的第二个参数指定想要的键。
db.center.find({},{"username":1,"email":1})
键值为1,则在查询结果中返回,键值为0,则在查询结果中剔除。

限制:传递给数据库查询文档的值必须是常量,不能引用文档中其他键的值.

查询条件:

db.center.find({"age":{"$gte":18,"$lte":30}}) --范围查询
$lt:less than $lte:less than and equel $gt:grate than $gte:grate than and equel $ne:not equel

db.center.find({"ticket_no":{"$in":[20,24,45]}}) --or查询
$nin:not in
$or:对多个键的匹配进行匹配
db.cneter.find({"$or":[{"ticket_no":45},{"username":"kasumi"}]})

$mod会将查询的值除以第一个给定值,若余数等于第二个给定的值则匹配成功.
db.center.find({"idnum":{"$mod":[5,1]}})
取反操作:db.center.find("idnum":{"$not":{"$mod":[5,1]}})

一个键可以有人以多个条件,但是一个键不能对应多个更改修饰器.

查询优化器不会对$and进行优化:
db.center.find({"$and":[{"x":{"$lte":1}},{"x":4}]})
db.center.find({"x":{"$lte":1,"$in":[4]}}) --查询效率更高

特定类型的查询:
null
db.center.find({"z":{"$in":[null]},"$exists":true}) --键值为null,并且键存在的文档
db.center.find({"z":null}) --键不存在的文档也会返回.

正则表达式:

查询数组:
db.cneter.find({fruit:{$all:["apple","banana"]}})
$all --通过多个元素来匹配数组,如果使用$all匹配一个元素,则和单独查询没有区别.
以上查询含有重复多行的数组,只返回一次;对只包含查询值得数组,也不会返回.
查询数组特定位置的值:db.cneter.find({"fruit.2":"peach"})
$size --查询特定长度的数组
db.center.find("fruit":{"$size":3}})
$slice --返回某个键匹配的数组元素的一个子集
db.blog.findone(criteria,{"comments":{"$slice":10}}) --返回前10条评论
db.blog.findone(criteria,{"comments":{"$sliice":-10}}) --返回后10条评论
db.blog.findone(criteria,{"comments":{"$slice":[20,30]}})
$slice会返回文档中所有的键
返回匹配的数组元素:db.blog.find({"comments.name":"bob",{"comments.$":1}})
只会返回第一个文档,如果bob有多条评论,只有第一条评论会被返回.

$elemMatch --同时使用查询条件中的恋歌语句与一个数组元素进行比较,但不会匹配非数组元素
db.test.find({"x":{"$elemMatch":{"$gt":10,"$lt":20}}})

内嵌文档的查询:
db.people.find({"name.first":"joe","name.last":"schmoe"}) --与查询顺序无关.
db.people.find({"name":{"first":"joe","last":"schmoe"}}) --文档必须精确匹配

正确指定一组条件,而不必指定每一键,需要使用$elemMatch:
db.blog.find({"comments":{"$elemMatch":{"author":"joe","score":{"$gt":5}}}})

$where:
db.foo.find({"$where":function(){
for(var current in this){
fro(var other in this){
if (current != other && this[current]==this[other]){
return true;
}
}
}
return false;
}})

服务器端javascript很容易受到注入攻击,可以在运行mongodn时指定--noscripting

游标:for(i=0;i<100;i++){
db.cillection.insert({x:i})
}
var cursor = db.collection.find();

db.center.find().limit(3):限制返回查询结果的数量
db.center.find().skip(3):略过查询结果的前三个返回值
db.center.find().sort({username:1,age:-1}):根据字段名进行排序,1:升序,-1:降序

对混合类型的键排序,其顺序排序是预先定义好的:
最小值<null<数字<字符串<对象/文档<数组<二进制数据<对象ID<布尔型<日期型<时间戳<正则表达式<最大值

避免skip跳过大量的数据:
var page1=db.foo.find().sort({"date":-1}).limit(100)
var latest=null
//显示第一页
while(page1.hashNext()){
latest=page1.next();
display(latest);
}
//获取下一页
var page2=db.foo.find({"date":{"$gt":latest.date}});
page2.sort({"date":-1}).limit(100);

选取随机文档:
//效率不高的使用方法

var total=db.foo.count()
var random = Math.floor(Math.random()*total)
db.foo.find().skip(random).limit(1)

可在插入数据的时候增加一个额外的随机键:
db.people.insert({"name":"joe","random":Math.random()})

var random =Math.random()
result= db.people.findOne({"random":{"$gt":random}})

高级查询选项:
$maxscan:指定查询中扫描文档数量的上限
db.foo.find(criteria)._addSpecial("$maxscan":20)
$min:查询的开始条件
$max:查询的结束条件
$showDiskLoc:用于显示该条结果在磁盘上的位置
db.foo.find()._addSpecial('$showDiskLoc',true)

查询的时候,游标右移,当问道那个返回数据库的时候,体积增大,预留的空间不足,会挪至集合的末尾处,导致游标到末尾。
解决:使用快照.db.foo.find().snapshot()
快照会使查询变慢,只在必要时使用快照.
数据库游标在应用程序一段时间不使用的时候,会自动销毁游标,为了延长游标的使用时间,使用immortal函数,使游标不超时.

数据库命令:
db.listCommands():查看所有的数据库命令
有些命令需要使用管理员权限:而且需要在admin库上才能执行.
在其他库执行管理员命令:
use temp
db.adminCommand({"shutdown":1})


设计应用

索引

使用explain()查看在查询过程所做的所有事情.
db.users.find({username:"kasumi"}).explain()

建立索引:db.users.ensureIndex({"username":1})
创建索引如果没能在几秒内返回,可在另外一个shell执行:db.currentOp()或者查看mongod的日志查看索引创建的进度.
缺点:
对于添加的每一个索引,每次写操作(插入、更新、删除)都会消耗更多的时间,数据发生变动的时候,不仅需要更新文档,还要更新集合上的所有索引.

查询结果的默认排序是升序.
结果集的大小超过32MB,数据库就会出错,拒绝对如此多的数据进行排序.

索引实质上是树,最小的值在最左的子叶上,最大的值在最右的子叶上.使用新数据较多--保留这棵树最右侧的数据,尽可能使索引右平衡.

低效率的操作符:
$where、$exists --不能使用索引
尽量使用$in而不是$or --两次查询将结果合并的效率不如单词查询的高,$or就是将两次查询的结果合并.

建立在文档上的索引,只有完全匹配的子文档才会使用索引,对子文档的查询,需要建立子文档的索引.

多键索引:这个键在某个文档中是一个数组,被标记为多键索引,被标记为多键索引后,只能删除索引再重建,才能恢复为非多键索引.

索引基数:集合中某个字段拥有的不同值得数量.
一个字段的基数越高,这个键上的索引越有用.
在基数较高的字段上建立索引,将基数较高的键放在复合索引之前.(基数较低的键之前)

hint():强制使用指定的索引
db.c.find({"age":14,"username":/.*/}).hint({"username":1,"age":1})
--默认使用("age":1,"username":1) --先进行精确匹配再进行模糊匹配

查询优化器:
如果有多个可用的索引,结果先返回的查询计划被缓存,其他查询会被中止.
explain()中allplans会显示所有尝试过的查询计划.

{"$natural":1} --强制使用全表扫描,返回结果是按照磁盘上的顺序排列的.

索引类型:

唯一索引:
db.users.ensureIndex({"username";1},{"unique":true}) --重复的值将不能插入
如果集合中已经存在重复值了,再创建唯一索引会导致失败.
db.users.ensureIndex({"username";1},{"unique":true,"dropDups":true}) --遇到重复值得时候会删除

稀疏索引:
db.ensureIndex({"email":1},{"unique":true,"sparse":true}) --字段可以没有值,但是当字段有值的时候必须是唯一的.

索引管理:

数据库索引信息存储在system.indexes集合中,这是一个保留集合,不能在其中插入或者删除文档
只能通过ensureIndex或者dropIndexes对其进行操作.

db.[collectionName].getIndexes():查看给定集合上的所有索引信息.

创建索引的时候,会阻塞所有对数据库的读请求和写请求,一直到索引创建完成为止.
创建索引的时候使用background选项,创建索引时,如果有新的数据库请求要处理,创建索引的过程会暂停一下.

特殊的索引和集合:

固定集合:

向已经占满的固定集合中插入数据,固定集合会将最老的文档从集合中自动删除.
db.createCollection("my_collection",{"capped":true,"size":10000})
将已有的常规集合转换为固定集合:db.runCommand({"convertToCapped":"test","size":10000})
自然排序:
db.my_collection,find().sort({"$natural":-1})

没有_id索引的集合:
调用createCollection创建集合的时候,指定autoIndexId为false.
没有_id索引的集合,不能复制所在的mongod.
2.2版本之前,固定集合默认是没有_id索引的.

TTL索引:

为每一个文档设置一个超时时间,一个文档达到预设的老化程度之后,就会被删除.
db.foo.ensureIndex({"lastUpdated":1},{"expireAfterSecs":606024})

全文本索引:

db.adminCommand({"setParameter":1,"textSearrchEnabled":true}) --启用全文本索引
权重:db.hn.ensureIndex({"title":"text","desc":"text","author":"text"},{"weights":{"title":3,"author":2}}) --默认值为1
索引一经创建就不能改变字段的权重了.

GridFS存储文件: --用来存储大型二进制文件
1.使用GridFS能够简化栈.
2.GridFS会自动平衡已有的复制或为mongodb设置自动分片,所以对文件存储做故障转移或者横向扩展会更加容易.
3.用于存储用户上传的文件时,GridFS可以比较从容得解决其他一些文件系统可能遇到的问题.
4.文件存储的集中度会比较高.
缺点:
1.性能比较低.
2.如果要修复GridFS上的文档,只能将已有的文档删除,然后再将整个文档重新保存.

from pyymongo import Connection
import gridfs
db = Connection().test
fs = gridfs.GridFS(db)
file_id = fs.put("hello ,kasumi",filename="foo.txt")
fs.list()
fs.get(file_id).read()

聚合:

聚合框架:
筛选filtering、投射projecting、分组grouping、排序sorting、限制limiting、跳过skipping

db.articles.aggregate({"$project":{"author":1}},{"$group":{"_id":"$author","count":{"$sum":1}}},{"$sort":{"count":-1}},{"$limit":5})

数学表达式:
$add: +
$subtract: -
$multiply: *
$divide: /
$mod: 求余
db.employees.aggregate({"$project":{"totalplay":{"$add":["$salary","$bonus"]}}})

字符串表达式:
$substr:在第一个参数的字符中,从第二个参数开始,截取第三个参数的字节数
$concat:将给定的字符串连接起来
$toLower:
$toUpper:

逻辑表达式:

数组操作符:
$addToSet:如果参数不在数组中,则将它添加到数组中.
$push:无条件的添加参数到数组中.

db.runCommand({"distinct":"people","key":"age"}) --指定集合和键

应用程序设计:

范式可以提高数据的写入速度,反范式能够提高数据的读取速度.

删除旧数据:
固定集合:当集合被填满的时候,将旧数据从固定集合中挤出.
TTL集合:更加精确的控制文档删除的时机.
定期删除文档:每月使用单独的文档,定期删除.

不适合使用mongodb的场景:
1.mongodb不支持事务.
2.在多个不同维度上对不同类型的数据进行连接.
3.使用工具不支持mongodb.

复制:

创建副本集:

客户端不能在备份节点执行写操作.
默认情况下,客户端不能从备份节点中读取数据.

副本集内的每个成员都必须能够连接到其他所有成员.

副本集的组成:

同步:

复制功能是使用操作日志oplog实现的,操作日志包含了主节点的每一次操作.oplog是主节点的local数据库中的一个固定的集合.
备份节点从当前使用的同步源中获取需要执行的操作,然后再自己的数据集上执行这些操作.最后将这些操作写入自己的oplog.

从应用程序连接副本集:

主节点挂掉后,应用程序不能执行读操作,如果希望主节点挂掉后,应用程序可以执行读操作,可以从备份节点读取数据.
从备份节点读取数据的时候,并不在意读到的数据是不是最新的.

低延迟的读和写:必须使用分片,副本集值允许在主节点上进行写操作.

管理:

将成员的投票数量设置为0:
rs.add({"_id":1,"host":"server-1:27017","vote":0})
当副本集中数量较多时,可以设置某些成员没有选举权限。

rs.reconfig(config,{"force":true}) --强制重新配置副本集

阻止选举:
如果需要对主节点做维护,但是不希望这段时间内将其他成员选举为主节点,那么可以在每个备份节点上执行freeze命令,强制他们处于备份节点的状态.
rs.freeze(10000)
释放:rs.freeze(0)

监控复制:

rs.status()

server1.adminCommand({replSetGetStatus:1})['syncingTo'] -查看服务器的同步源

增加oplog的大小:
1.如果当前节点是主节点,让它退位,以便让其他成员的数据能够尽快更新到与它一致.
2.关闭当前服务器.
3.将当前服务器以单机模式启动.
4.临时将oplog的最后一条insert操作保存到其他集合中.
5.删除当前的oplog:db.oplog.rs.drop()
6.创建一个新的oplog:db.createCollection("oplog.rs",{"capped":true,"size":10000})
7.将最后一条操作记录写回oplog
确保数据插入成功,如果没有插入成功,将服务器添加到副本集后,会删除所有数据。
8.将服务器作为副本集重新启动.

主从设置:
mongod --master
mongod --slave --source masterip:port

主从模式切换到副本集模式:
1.停止系统所有的写操作.
2.关闭所有的mongod服务.
3.使用--replSet选项重启主节点.
4.初始化这个只有一个成员的副本集.
5.使用--replSet和--fastsync选项启动从节点.
6.使用rs.add()将之前的从节点加入副本集.
7.对每个从节点重复5-6步.
8.当所有从节点变为备份节点之后,就可以开启系统写的功能了.
9.从配置文件、命令行和内存中删除fastsync选项.

分片:

分片:

sh.status()可以看到集群的状态:分片摘要信息、数据库摘要信息、集合摘要信息.
主分片与副本集中的主节点不同.主分片指的是组成分片的整个副本集,而副本集中的主节点指的是能够处理写请求的单台服务器.

要对一个集合分片,首先要对这个集合的数据库启用分片:sh.enableSharding("center")
在启用分片之前,现在希望作为片键的键上创建索引:db.users.enableIndex({"username":1})
根据username对集合进行分片:sh.shardCollection("center.users",{"username":1})

如果有多个现存的副本集没有作为分片,只要他们没有同名的数据库,就可以将它们作为全新的分片全部添加到集群中.

db.locks.findOne({"_id":"balancer"}) --查看mongos的均衡器.

mongos:
use config
db.shards.find() --查看集群内所有的分片信息.
db.databases.find() --跟踪记录所有数据库的信息.
db.collection.find() --跟踪记录所有分片集合的信息.
chunks --记录集合中所有块的信息
changelog --跟踪记录集群的操作
db.tags.find() --为系统配置分片标签时发生的.
settings --当前均衡器设置和块大小的文档信息.

查看连接统计:db.adminCommand({"connPoolStats":1})
host1这样的主机名是来自配置服务器的连接,name/host1这样的主机是来自分片的连接.
在mongos和mongod上运行命令才会有效.

添加服务器:
只要保证在mongos的--configdb选项中指定了一组正确的配置服务,mongos可立即与客户端建立连接.

修改分片的服务器:
只有在使用单机服务器作为分片,而不是使用副本集作为分片时,才需手动修改config.shards.

将单机服务器分片修改为副本集分片:
1.停止向系统发送请求
2.关闭单机服务器server1和所有的mongos进程
3.以副本集模式重启服务器server1
4.连接到server1,将其作为一个但成员副本集进行初始化
5.连接到配置服务器,替换该分片的入口,在config.shards中将分片的名称替换.

删除分片:
db.adminCommand({"removeShard":"server1"})
保证均衡器开启的情况下,删除分片的时候,会将数据迁移至其他分片.
查看执行的状态,可以再次执行以上命令,查看当前的状态.

数据均衡:
sh.setBalancerState(false) --执行所有的数据库管理操作执勤啊,都应该先关闭均衡器.
db.locks.find({"_id":"balancer"})["state"] --检查均衡器是否关闭

刷新配置:
db.adminCommand({"flushRouterConfig":1}) --手动刷新所有缓存.

应用管理:

查看正在进行的操作:
db.currentOp()

使用系统分析器来查找耗时过长的操作,但是mongod的整体性能也会有所下降,因此只定期打开分析器来获取信息.
db.setProfilingLevel(2)
将分析级别设为0可关闭分析器:db.setProfilingLevel(0)

mongotop --查看那个集合最繁忙
mongostat --提供有关服务器的信息,默认每秒输出一次包含当前状态的列表.

数据管理:

身份认证:
分片时,数据库admin会被保存在配置服务器上,所以分片中的mongod甚至并不知道它的存在,
因此,在它们看来,它们虽然开启了身份认证但却不存在管理员用户。于是,分片会允许一个本地的local客户端无需身份认证便可读写数据.
将网络配置为止允许客户端访问mongos进程即可.不过,如果担心客户端在分片的本地上运行,不通过mongos进程而直接连接到分片的话,可在分片中添加管理员用户.

移动集合:
无法在数据库间移动集合,但可更改集合名:db.sourceColl.renameCollection("newname")
将一个集合移动到另一个mongod中:db.runCommand({"cloneCollection":"collName","from":"histname:port"})

持久性:

mongod内置恢复工具:mongod --dbpath /data/gst/mongodb2.6.12/data --repair [--repairpath /data]:指定恢复的目录

mongodump --repair

mongod.lock文件:如果启用了日记系统,则这一文件不会出现
mongod正常退出的时候,会清除这一文件,如果没有清除文件,则说明mongod非正常退出.
mongod.lock文件会阻止mongod的启动,如果mongod.lock阻止了mongod的启动,不能单纯的删除mongod.lock文件,需要对数据进行修复.

服务器管理:

启动和停止mongodb:

--dbpath
--port
--fork
--logpath
--directoryperdb:将每个数据库存放在单独的目录中
--config
启动数据库的时候,mongodb会将一个文档写入local.startup_log >db.startup_log.find()

关闭:
admin库:db.shutdownServer()
db.adminCommand({"shutdown":1,"force":true}) --强制关闭

监控mongodb:

备份:

复制数据文件:
db.fsyncLock() --锁定数据库,禁止任何写入,并进行同步.所有的写入操作加入队列等待.
db.fsyncUnlock()

mongodump / mongorestore:--drop在恢复一个集合前先删除.

对副本集进行备份:
对备份节点进行备份,减轻主节点的负担.
使用mongodump在备份的时候,需要使用--oplog,得到基于某个是简单的快照,否则备份的状态不会和任何其他集群成员的状态相吻合.
恢复的时候需要运行--oplogReplay.

对分片集群进行备份:
整个集群:
关闭均衡器,通过mongos运行mongodump,恢复备份需要运行mongorestore并连接到一个mongos.

mongooplog进行增量备份:
1.记录下A的oplog中最近一次的操作时间
op=db.oplog.rs.find().sort({$natural:-1}).limit(1).next();
start =op['ts']['t']/1000
2.对数据进行备份,恢复备份至B上的数据库目录
3.定期添加A上的操作至B,从而完成数据的复制.
mongooplog --from A --seconds 1234567
seconds后的参数,应该为第一步中计算得到的start变量和当前时间的差值,再额外加上几秒.

部署mongodb: