mongoDB的一些概念及实践

前言

MongoDB 是由C++语言编写的,是一个基于分布式文件存储的开源数据库系统。具有高负载,高性能的优势。MongoDB 将数据存储为一个文档,数据结构由键值(key=>value)对组成。MongoDB 文档类似于 JSON 对象。字段值可以包含其他文档,数组及文档数组。

一些概念/术语

sql术语|MongoDB术语|释义 —|—|—| database|database|数据库 table|collection|数据库/集合 row|document|数据库记录行/文档 colunm|field|数据字段/域 index|index|索引 table joins| |表连接,mongdb不支持 primary key|primary key|主键,mongdb使用_id字段设置主键

objectId组成

mongdb指令

创建数据库:use 如果数据库不存在,则创建数据库。(只有真正往集合里写文档时才创建)

显示所有数据库:show dbs 查看所有数据库

新建数据库只有在插入文档后才能在show dbs中显式

删除数据库:db.dropDatabase() 删除当前数据库

创建集合:db.createCollection(,)

options 选项:

固定集合不允许正常集合所有操作,增加/修改等操作

显示所有集合:show tables

删除集合:db..drop()

插入文档:db..insert() insert方法内支持对象和数组(mongdb允许我们在不知道数据结构情况下插入数据) (也可以使用insertOne和insertMany方法插入文档)

BSON文档大小限制16M,文档嵌套深度最大为100

更新文档

删除文档

db..remove(,{justOne:,writeConcern:})

也可以是deleteOne deleteMany方法

remove() 方法并不会真正释放空间。需要继续执行db.repairDatabase()来回收磁盘空间。 **drop() 附带删除索引

查询文档

db.collection.find(query,projection) // 通常只会返回20条数据

find() 查询返回多个文档 findOne() 查询返回单个文档

pretty()格式化返回结果

统计 db.collection.count()

比较符 操作|mongdb查询条件|sql查询条件 —|—|—| 等于|db.col.find({“name”:”yates”}).pretty()|where name = ‘yates’ 小于|db.col.find({“age”:{lt:50}}).pretty()|where age < 50 小于或等于|db.col.find({“age”:{lte:50}}).pretty()|where age <= 50 大于|db.col.find({“age”:{gt:50}}).pretty()|where age > 50 大于或等于|db.col.find({“age”:{gte:50}}).pretty()|where age >= 50 不等于 |db.col.find({“age”:{ne:50}}).pretty()|where age != 50

AND mongdb and 条件使用“,”隔开 db.<col>.find({“age”:”12”,”sex”:”m”})

$OR $and $ne $not $nor $exists 布尔运算符 mongdb and 条件使用“&or” db.<col>.find({$or:[{“age”:”12”},{“sex”:”m”}]})

查询类型

使用条件操作符 “$type”进行collum的类型匹配

db.col.find({“title” : {$type : ‘string’}})

limit skip 使用limiit()限制返回条数 db..find(). limit()

使用skip()方法来跳过指定数量的数据 db..find().limit().skip()

当skip大量数量时 也会和rdbms产生一样的效率问题

$in $all $nin查询运算符 db..find({'type':{'\$in':['type1','type2','type3']}}) 查询类型为type1或type2或type3其中一个的集合

子文档查询 查询属性中颜色为红色的文档 db..find({'attr.color':"red"})

数组查询 查询文档中数组中爱好含有football的文档 db..find({'hobby':"football"}) 查询文档中数组中爱好**首位**为football的文档 db..find({'hobby.0':"football"}) 查询文档中数组中爱好**数量**为N个时的文档 db..find({'hobby':{\$size:n}})

$elemMatch 匹配子文档多个字段时 db..find({'subdocument':{'\$elemMatch':{'col1':'value1','col2':'value2'}}})

排序 使用 sort() 方法对数据进行排序,1为升序排列,而-1是用于降序排列 db..find().sort({age:1})

** 返回指定字段** 只返回name字段的数据 db..find({},{'name':1}).pretty()

返回文档中数组指定skip,limit数量数据 db..find({},{'hobby':{\$slice:[3,4]}}).pretty()

除name字段都返回的数据 db..find({},{'name':0}).pretty()

** 模糊查询** 模糊查询匹配test前缀的数据 db..find({'titile':/^test/}).pretty()

创建索引 使用 createIndex() 方法来创建索引

db..createIndex(, )

查看索引

利用 TTL 集合对存储的数据进行失效时间设置:经过指定的时间段后或在指定的时间点过期,MongoDB 独立线程去清除数据。类似于设置定时自动删除任务,可以清除历史记录或日志等前提条件,设置 Index 的关键字段为日期类型 new Date()

db.collection.insert({
	time_field:new date(),
})

db.collection.createIndex({time_field:1},{expireAfterSeconds:3600}) 

1 不能_ID字段建立TTL 2 不能在已经建立索引字段建立TTL 3 不能在盖子集合里使用 如果索引字段有一组时间戳,也不能组合TTL索引

数据聚合

aggregate()方法主要用于处理数据(诸如统计平均值,求和等

db..aggregate("索引名称")

db.mycol.aggregate([{$group : {_id : “$by_user”, num_tutorial : {$sum : 1}}}])

表达式 描述
$sum 计算平均值
$avg 获取集合中所有文档对应值得最小值
$min 获取集合中所有文档对应值得最大值
$push 在结果文档中插入值到一个数组中
$addToSet 在结果文档中插入值到一个数组中,但不创建副本
$first 根据资源文档的排序获取第一个文档数据
$last 根据资源文档的排序获取最后一个文档数据

管道聚合查询 MongoDB的聚合管道将MongoDB文档在一个管道处理完毕后将结果传递给下一个管道处理

文档重塑函数 当为分组重新定义_id的时候,我们可以使用一个或多个存在的字段,进行重塑,只有_id可以使用重塑功能,其他字段不能使用例如max,min,avg等函数

查询某商品评价数量和评分 product = db.products.find({‘slug’:’wheelbarrow-9092’}) ratingSummary = db.reviews.aggregate([ {$match:{‘product_id’:product[‘_id’]}}, {$group:{_id:’$product_id’,//以产品id分组 average:{$avg:’rating’},求平均评价分 count:{$sum:1}}}求评价数量 ]).next();

因为mongodb不允许自动化连接其他集合,所以在一般开发中使用冗余数据来解决,还有一种解决方案,通过伪连接来处理 db.tempCatSumary.remove();

product = db.products.find({‘slug’:’wheelbarrow-9092’}) ratingSummary = db.reviews.aggregate([ {$group:{_id:’$cat_id’,//以产品分类分组 count:{$sum:1}}}]).foreach(function(doc){ var cat = db.cats.findOne({_id:doc._id}); if(cat != null) { doc.catName = cat.name; }else{ doc.catName = ‘not found’} } db.tempCatSumary.insert(doc); });

通过out连接操作符可以直接输出到集合(如果集合不存在,则out创建一个集合,存在就会完全取代现有集合) ratingSummary = db.reviews.aggregate([ {$group:{_id:’$cat_id’,//以产品分类分组 count:{$sum:1}}}, {$out:’ratingSummary’} ])

聚合管道选项 影响管道性能点

db.collection.aggregate(pipeline,additionaloptions) 通过聚合管道选项优化管道查询

map-reduce 通过编写JavaScript定义整个处理流程,有点事提供了巨大的灵活性,缺点是较聚合函数比较复杂

文档数据类型

字符串:必须为UTF-8编码 数字:double,long,int(Bson数值类型最常见问题是缺少小数位支持,这意味我们存储货币数据值,需要使用整数类型,单位为分) 时间:使用从unix纪元计时开始64b整数的毫秒值表示。

mongodb特性

  1. 数据结构,mongdb使用文档数据模型,不强制使用schema带来的好处是,应用程序的代码强制数据结构而不是数据库。普通sql关联查询会很复杂,而文档建模不需要关联,可以动态添减属性
  2. ad hoc查询(不需要事先定义系统接收何种查询),
  3. 索引,mongodb中索引使用了B-树,每个集合可以创建64个索引,每个文档对象建立一个主键,这也就是为什么mongodb不同于hbase之类nosql区别。
  4. 复制,mongodb提供数据库复制特性,可复制集合在多个机器上分布式存储数据,在服务器和网络出错时,实现数据冗余存储和自动备灾;也常用于读写分离的场景
  5. 加速与持久化,mongodb也和mysql一样通过日志来保证写入的可靠性
  6. 伸缩,通过基于分区机制来实现水平扩展,称为分片机制。

mongdb驱动

所有mongdb驱动都执行三个主要功能:

  1. 生成mongdb对象ID。默认存储在所有文档的_id字段
  2. 将任意于洋表示的文档对象转换为BSON(二进制JSON格式)或从Bson转换回来
  3. 最后使用TCP socket与数据库连接通信,

mongodb数据构建技巧

  1. 在插入文档的时候,比如商品之类的文档,可以定义一个URL slug,这样构建有意义的URL,利于SEO
  2. 有效的key名由最大255B长度字符串组成,尽量短的key能节约大量的空间

mongodb复杂查询

db.orders.find({user_id}:user[‘_id’]) 查询某个用户的订单

mongodb的数据库文件存储在启动mongod时dbpath参数指定的目录文件夹(不指定时,会在/data/db文件夹里存储数据) mongdb中文档中相同字段可以有不同存储数据类型,所以对字段进行条件查询时,需要根据不同类型继续条件设置(比如value:97和value:’a’ 的比较 findOne({‘value’:{‘$gt’ 97}}) findOne({‘value’:{‘$gt’ ‘a’}}))

mongodb原子操作

mongdb更新分为目标更新和替换更新 // update默认更新第一个文档,可以使用multi true 大量更新 db.collection.update({},{‘key’:’value’},{multi:true}) // 文档更新,如果不存在时插入数据 db.collection.update({‘key’:’value’},{$addToSet:{tags:’tt’}},{upsert:true})

更新操作符

在2.2之前,锁策略很简单,单个读/写驻留在整个MongoDB实例中。任意时刻,MongoDB至于徐一个写或多个读操作,2.2改为数据库级别的锁,3.0后wiredTiger存储引擎在集合级别的锁。针对写锁,如果某个写操作很长,那么这种写只有为其他读/写让路,屈服,待锁释放后再重新启动

mongodb的索引

mongdb索引绝大部分都是使用B树实现 mongodb的唯一索引,创建后,只有在插入数据的时候才会报唯一键的错误;对于已经存在的重复键可以在创建索引的时候使用dorpDups:true删除重复键(随机) db.collection.createIndex({‘key’:1},{unique:true,dropDups:true})

对于一些数据的字段在插入时是为空的,那么如果是唯一索引会导致插入失败或大量的null值产生,这种普通密集型索引不是我们想要的,mongodb提供稀疏索引(只有在字段上有值的才会加入索引树)解决这个问题 db.collection.createIndex({‘key’:1},{sparse:true,unique:false})

多建索引:多个索引入口或键值,引用同一个文档 哈希索引:哈希索引通过均匀分散这些文档来解决集合产生时大量写请求或大量集中相邻数据访问的读压力,也就是分片存储

地理空间索引:基于经纬度来存储每个文档

mongodb的索引文档存储在专门的system.indexes里面

mongodb对于执行时间超过100ms的语句存入慢查日志,便于开发人员进行分析

profiler分析某个数据查询性能 db.setProfilingLevel(level,time) //level表示监控级别,time表示监控下限执行时间毫秒数 监控下限会保存到一个特殊的固定集合system.profile

mongodb提供explain分析语句执行详细信息,关键参数含义

mongodb查询优化器优化规则:

mongodb做全文索引

所有的文本搜索引擎提供的功能逗比MongoDB强大,在类别搜索中MongoDB仍可提供80%的功能,使用更小的复杂性和精力建立包含方面搜索好词汇提供的全文搜索引擎。

mongodb能做什么?

mongodb怎么做

mongodb每个索引入口为文档中每个词根单词创建。 索引必须包含集合文档中每个唯一的词根单词,而且还有指向被建立索引的文档。所以文本索引的大小会比集合还大。可以使用通配符$**匹配人员字段中包含的字符串

搜索字符串被分割为多个单词,分词被删除,剩余单词提取词根,不区分大小写,使用建立的文本索引进行文档的搜索

使用双引号精确匹配短语和词 (精确匹配不会区分大小写比较) db.collections.find({$text:{$search:’“mongodb” “second edition”’}})

使用 - 排除匹配单词文档 db.collections.find({$text:{$search:’“mongodb” “-second edition”’}})

一个集合只有一个文本索引

使用find结合文本搜索执行更多条件查询

使用score得到文本搜索分数 db.collections.find({$text:{$search:’mongodb in action’}}, {title:1,score:{$meta:”textScore”}})

根据文本搜索分数排序 db.collections.find({$text:{$search:’mongodb in action’}}, {title:1,score:{$meta:”textScore”}}).sort({socre:{$meta:”textScore”}})

文本搜索分数跟字段权重成倍数关系

使用聚合框架结合文本搜索$text配合使用 db.collection.aggregate( [{$match:{$text:{$search:’mongodb in action’}}}, {$project:{title:1,score:{$meta:’textScore’}}}, {$sort:{score:-1}}] )

限制

mongDB文本搜索有其局限性,缺少字段可能导致错过一些搜索结果。文本搜索提供强制搜索结果包含某些关键字,聚合框架也提供额外灵活性和功能对错过结果进行改进。

因为mongoDB不支持自定义分词列表,mongoDB基于不同语言使用不同的分词词典。可以通过在索引,文档,搜索关键字指定使用语言来达到某些文本误过滤

WiredTiger存储引擎

存储引擎并不会改变shell或驱动里执行的查询,也不会在集群级别干扰MongoDB,但是存储引擎影响如何从磁盘写入,删除和读取数据,还有存储数据使用的数据结构

mongoDB默认存储引擎是MMAPv1,如果要使用WiredTiger引擎的数据库,我们需要把数据库进行迁移,通过创建镜像并恢复镜像来实现

ZLib版本可以获得最好的性能和开销因素,对于不关注存储开销的应用系统,无压缩模式的WirediredTiger存储引擎配置时最佳的选择。使用snappy压缩算法,会比zlib配置获得更好的速度

B-树从根节点开始,在这个节点中,我们使用键值存储数据记录。这些记录可能由指针指向实际的值,或在记录里直接保存数据。每个节点有几个数据记录,通过索引键值来建立索引,每个记录中都有一个指针占位符可以指向其他节点。左节点比右节点小,节点内左侧数据比右侧数据小,节点大小通常为磁盘块的大小

WiredTiger往往能够提供更好的读/写操作,而且提供文档级别的锁,比较节约磁盘的空间。wiredTiger的性能与成本远远优于MMAPv1存储引擎

复制

mongodb支持主从复制和可复制集群两种机制。他们都是用相同的复制机制。后者额外增加了自动化灾备机制。

主从复制不足:如果主节点发送故障,管理员必须关闭一个从服务器,然后作为主节点重新启动,然后应用程序必须重新配置连接新的主节点。因为oplog只在主节点存在,需要在新的服务器创建新的oplog,并且重新从主节点同步oplog。

复制首先是为冗余设计的,它可以确保从节点与主节点数据同步。还有一种使用情况是灾备。

可复制集群最小配置包含3个节点,一个裁判节点和2个数据节点,通过选举得到可用节点选择新的主节点

原理 oplog是个盖子集合,它存在于每个复制节点的local数据库里,而且记录所有数据变化。local数据库存储了所有可复制集群中元数据和oplog操作日志。主节点写数据,写操作被记录并添加到主节点oplog里,同时所有从节点都会复制主节点oplog。当从节点准备更新自己数据时,首先,查看自己oplog里最新记录的时间戳,然后查询主节点oplog,查询所有时间戳大于当前自己时间戳的oplog记录,最后写入数据并添加每个操作日志到自己oplog

停止复制的情况:如果从节点无法在主节点找到oplog同步日志记录点,就会永久停止复制。其原因可能是因为从节点的延迟或离线一段时间,导致主节点oplog同步日志记录点被覆盖。解决方案:通过扩大oplog大小。oplog大小可以通过测试应用在正常生产环境下一小时写入量。然后获得至少8小时的写入量作为oplog大小

如果从节点和裁判都被杀死的情况下或因为网络分区导致主节点看不到其他节点,那么主节点会退化为从节点。避免一个应用产生两个主节点的情况。

当主节点的写入操作被复制到大多数从节点时,主节点才会提交数据,通过设置驱动的写关注点来控制在返回之前必须有多少个节点被写入,增加这个值可增加持久性,但是会带来一定的延迟成本。

mongodb通过读偏好和标签控制更复杂的可复制集的读/写行为

分片集群

分片是把大型数据集进行分区成更小的可管理的片的过程

什么时候分片:负载分布式,存储分布式

分片集群组成:

mongos路由器每次启动都会从配置服务器获取一份元数据拷贝,但写入配置服务器时,使用两阶段提交协议保证配置服务器一致性

分片键 根据一个或一组字段的值进行逻辑分组

分片集群的数据分散方式

mongodb每一个块数据量达到设定最大值时,会发生数据迁移,以保持数据分布的均匀

查询路由分为目标性查询和全局或分散/集中查询。前者传递给find查找方法的查询包含分片键,mongos可以快速咨询数据块数据确定所属分片;后者查询分片键不属于查询,查询集合就必须访问所有分片服务器已填充查询

分片集群的索引在每个分片进程都会单独的创建,而且只允许在_id字段和分片键上建立唯一索引

分片选择的陷阱:

mongodb一个分片无法通过检查另外一个分片来查看分片键是否唯一。最好在客户端强制唯一性,那么两个相同分片键值的文档迁移到同一个分片时就会丢失数据

没有万能的分片键值可以覆盖所有的用例,只有思考自己期望的读写模式是什么。比如基于邮件系统基于发送者分片和基于接收者分片

部署

32位系统会限制MongoDB位2GB的存储空间,所有生产环境下建议使用64位架构 TODO