Mongodb 笔记04 特殊索引和集合、聚合、应用程序设计
特殊索引和集合
1. 固定集合:固定集合需要事先创建好看,而且它的大小是固定的。当固定集合被占满时,如果再插入新文档,固定集合会自动将最老的文档从集合中删除。
2. 创建固定集合:db.createCollection("my_collection",{"capped":true,"size":10000}) 创建一个大小为10000字节的固定集合
除了大小,createCollection还能指定固定集合中文档的数量:db.createCollection("my_collection",{"capped":true,"size":10000,"max":10})
指定文档数量时,必须同时指定集合大小。固定集合的文档数量不能超过文档数量限制,固定集合的大小也不能超过大小限制。
3. 将某个常规集合转换为固定集合:db.runCommand({"convertToCapped":"test","size":10000}) 无法将固定集合转换为非固定集合。
4. 自然排序:自然排序返回结果集中文档的顺序就是文档在磁盘上的顺序。只有固定集合中的自然排序才有意义,对其他集合来说文档位置经常变化,自然排序意义不大。
5. 循环游标:一种特殊的游标,当循环游标的结果集被取光后,游标不会被关闭。当有新文档插入到集合时,循环游标会继续取到结果。由于普通集合并不维护文档的插入顺序,循环游标只能用在固定集合上。
6. 调用createCollection创建集合时指定autoIndexId选项为false,创建集合时就不会自动在"_id"上创建索引。实践中不建议这么用,但对于只有插入操作的集合来说,这确实可以带来速度上的稍许提升。
7. TTL索引:如果需要更加灵活的老化移出系统,可以使用TTL索引,这种索引允许为每一个文档设置一个超时时间。一个文档到达预设置的老化程度之后就会被删除。这种类型的索引对于缓存问题很有用。
db.foo.ensureIndex({"lastUPdated":1},{"expireAfterSecs":60*60*24}) // 过期时间24小时
MongoDB每分钟对TTL索引进行一次清理,所以不应该依赖以秒为单位的时间保证索引的存活状态。可以使用collMod命令修改expireAfterSecs的值:
db.runCommand({"collMod":"someapp.cache","expireAfterSecs":3600})
一个给定的集合上可以有多个TTL索引,但TTL不能是复合索引。
8. 全文本索引:暂不建议使用,性能影响太大。
9. 地理空间索引:最常用的是2dsphere索引(用于地球表面类型的地图)和2d索引(用于平面地图和时间连续的数据)
10. 使用GridFS存储文件:GridFS是MongoDB的一种存储机制,用来存储大型二进制文件。
优点:
a. 简化你的栈,可以使用GRIDFS来代替独立的文件存储工具。
b. 对文件存储做故障转义或者横向扩展更容易
c. 可以比较从容地解决其他一些文件系统可能遇到的问题。
d. 文件存储的集中度会比较高
缺点:
a. 性能低:从MongoDB中访问文件,不如直接从文件系统中访问文件速度快。
b. 如果要修改GridFS上的文档,只能先将已有的文档删除。
通常来说,如果你有一些不常改变但是经常需要连续访问的大文件,那么使用GridFS再合适不过。(性能呢?因为缓存到内存了?)
11. GridFS背后的理念:可以将大文件分割为多个比较大的块,将每个快作为独立的文档进行存储。有一个文档用于将这些块组织在一起并存储该文档的元信息。
12. 可以使用任何自定义的字段来保存必须的文件元信息。可能你希望在文件元信息中保存文件的下载次数,MIME类型或者用户评分。
聚合
如果你有数据存储在MongoDB中,你想做的可能就不仅仅是将数据提取出来那么简单;你可能希望对数据进行分析加以利用。本章介绍MongoDB提供的聚合工具
1. 聚合框架:使用聚合框架可以对集合中的文档进行变化和组合。基本上,可以用多个构架创建一个管道,用于对一连串的文档进行处理。这些构件包括筛选、投射、分组、排序、限制和跳过。
2. 管道操作符:每个操作符都会接受一连串的文档,对这些文档做一些类型转换,最后将转换后的文档作为结果传递给下一个操作(对于最后一个管道操作符,是将结果返回给客户端)。不同的
管道操作符可以按任意顺序组合在一起使用,而且可以被重复任意多次。
1). $match : $match用于对文档集合进行筛选,之后就可以在筛选得到的文档子集上做聚合。在实际使用中应该尽可能将"$match"放在管道的前面位置,这样有两个好处:
a. 可以快速将不需要的文档过滤掉,以减少管道的工作量
b. 如果在投射和分组之前执行"$match",查询可以使用索引
用法:{$match:{"state":"OR"}}
2). $project : 可以从文档中提取字段,可以重命名字段,还可以进行一些其他操作。
用法:db.articles.aggregate({"$project":{"author":1,"_id":0}}) 只返回author字段。
字段重命名:{"$project":{"userId":"$_id","_id":0}} $fieldname语法是为了在聚合框架中引用fieldname字段的值。重命名后不能使用原字段上的索引。
数学表达式:
a. $add : [expr1[,expr2....exprN] 这个操作符是接收一个或多个表达式作为参数,将这些表达式相加
b. $subtract : [expr1,expr2] 接收两个表达式,用第一个表达式减去第二个表达式作为结果
c. $multiply : [expr1[,expr2.....exprN] : 将参数相乘
d. $divide : [expr1,expr2] 第一个表达式除以第二个表达式的商作为结果
e. $mod [expr1,expr2] 第一个表达式除以第二个表达式的余数作为结果
日期表达式
$year ,$month,$week,$dayOfMonth,$dayOfWeek,$dayOfYear,$hour,$minute,$second
字符串表达式:
a. $substr : [expr,startOffset,numToReturn] : 截取字符串,从startOffset字节开始的numToReturn字节。注意是字节不是字符。
b. $concat : [expr1,expr2,....exprN] : 将给定的表达式(或者字符串)连接在一起作为返回结果
c. $toLower : expr 返回小写形式
d. $toUpper: expr 返回大写形式
逻辑运算符:
$cmp:[expr1,expr2] : 比较expr1和expr2。如果expr1等于expr2,返回0,;如果expr1小于expr2;返回负数;如果大于,返回正数
$strcasecmp:[string1,string2] : 比较字符串大小
$eq/$ne/$gt/$gte/$lt/$lte:[expr1,expr2]
几个布尔表达式:
$and:[expr1,expr2...exprN] 如果所有表达式都为true,则返回true,否则返回false
$or : [expr1,expr2...exprN] 有一个为true,则返回true
$not : expr 对表达式取反
两个控制语句:
$cond :[booleanExpr,trueExpr,falseExpr] : 如果booleanExpr为true,返回trueExpr,否则返回falseExpr
$ifNull : [expr,replacementExpr] : 如果expr为null,返回replacementExpr,否则返回expr
3). $group : $group 操作可以将文档依据特定字段的不同值进行分组。
4). $unwind : 拆分(unwind) 可以将数组的每一个值拆分成单独的文档。
5). $sort : 可以根据任何字段进行排序,与在普通查询中的语法相同。如果要对大量的文档进行排序,建议在管道的第一阶段进行排序,这样可以使用索引。
6). $limit : 接收一个数字n,返回集合中的前n个字段
7). $skip : 接收一个数字n,丢弃结果集中的前n个文档。
3. 使用管道:应该尽量在管道的开始阶段就将尽可能多的文档和字段过滤掉。管道如果不是直接从原先的集合中使用数据,那就无法再筛选和排序中使用索引。如果可能,聚合管道会尝试对操作进行排序,
以便能够有效的使用索引。如果MongoDB发现某个聚合操作占用了20%以上的内存,这个操作就会直接输出错误。
如果能够通过$match操作迅速减小结果集的大小,就可以使用管道进行实时聚合。
4. MapReduce : 非常强大,非常灵活。但速度慢,不应该用在实时的数据分析中。
5. 聚合命令:
1). count : db.foo.count()
2). distinct : db.runCommand({"distinct":"people","key":"age"})
3). group
应用程序设计
1. 范式化:将数据分散到多个不同的集合,不用集合之间可以相互引用数据。虽然很多文档可以引用某一块数据,但是这块数据只存储在一个集合。
反范式化:将每个文档所需的数据都嵌入在文档内部。每个文档都拥有自己的数据副本,而不是所有文档共同引用同一个数据副本。
范式化能够提高数据写入速度,反范式化能够提高数据读取速度。
2. 更适合内嵌:子文档较小,数据不会定期改变,最终数据一致性即可,文档数据小幅增加,数据通常需要执行二次查询才能获得,快速读取
更适合引用:子文档较大,数据经常变化,中间阶段的数据必须一致,文档数据大幅增加,数据通常不包含在结果中,快速写入
3. 优化数据操作:如果要优化应用程序,首先必须知道对读写性能进行评估以便能找到性能瓶颈。对读取操作的优化通常包括正确使用索引,以及尽可能将所需信息放在单个文档中返回,
对写入操作的优化通常包括减少索引数量以及尽可能提高更新效率。
4. 经常需要再写入效率更高的模式与读取效率更高的模式之间权衡,所以必须要知道哪种操作对你的应用程序更重要。这里的影响因素并不只是读取和写入的重要性,也包括读取和写入操作的频繁程度.
5. 数据优化方式:
1). 优化文档增长:更新数据时,需要明确更新是否会导致文件体积增大,以及增长程度。如果增长程序是可预知的,可以为文档预留足够的增长空间,这样可以避免文档移动,提高写入速度。
检测一下填充因子,如果它大约是1.2或者更大,可以考虑手动填充。如果要对文档手动填充,可以在填充文档时创建一个占用空间比较大的字段,文档创建成功之后删除这个字段。
插入时添加或者在upsert时,使用$setOnInsert创建这个字段。更新文档时,使用$unset移除。如果这个字段不存在,$unset操作什么也不做。
可以考虑将会增长的字段,放在最后面。
2). 删除旧数据,主要有三种方式:使用固定集合,使用TTL集合,使用定期删除集合。
6. 一致性管理:
服务器为每个数据库连接维护一个请求队列。客户端每次发来的新请求都会添加到队列末尾。入队之后,这个连接上的请求会一次得到处理。一个连接拥有一个一致的数据库视图,可以总是读取到这个
连接最新写入的数据。注意,每个队列只对应一个连接:如果打开两个shell,连接到相同的数据库,这时就存在两个不同的连接。如果在齐中一个shell中执行插入操作,紧接着在另一个shell中执行
查询操作,新插入的数据肯呢个不会出现在查询结果中。
7. 不适合使用MongoDB的场景:
1). MongoDB不支持事务
2). 无法在多个维度上对不同类型的数据库进行连接。
3). 你使用的工具不支持MongoDB