本文完成时MongoDB的最新版本为MongoDB 2.6
1、count统计结果错误
这是由于分布式集群正在迁移数据,它导致count结果值错误,需要使用aggregate pipeline来得到正确统计结果,例如:
db.collection.aggregate([{$group: {_id: null, count: {$sum: 1}}}])
引用:“On a sharded cluster, count can result in an inaccurate count if orphaned documents exist or if a chunk migration is in progress.”
参考:http://docs.mongodb.org/manual/reference/command/count/
2、从shell中更新/写入到文档的数字,会变为float类型
引用:“shell中的数字都被MongoDB当作是双精度数。这意味着如果你从数据库中获得的是一个32位整数,修改文档后,将文档存回数据库的时候,这个整数也就被换成了浮点数,即便保持这个整数原封不动也会这样的。”
参考:《MongoDB权威指南》第一版
3、restore数据到新DB时,不要去先建索引
把bson数据文件restore到另一个DB时,需要注意:不能先创建索引再restore数据,否则性能极差,mongorestore工具默认会在restore完数据时,根据dump出来的index信息创建索引,无须自己创建,如果是要更换索引,也应该在数据入库完之后再创建。
4、DB中的namespace数量太多导致无法创建新的collection
错误提示:error: hashtable namespace index max chain reached:1335,如何解决呢?
这是DB中的collection个数太多导致,在实践中以每个collection 8KB计算(跟官方文档里说的不同,可能跟index有关系),256MB可以支持36000个collection。db.system.namespaces.count() 命令可以统计当前DB内的collection数目,DB可支持collection数量是由于nssize参数指定的,它指定了dbname.ns磁盘文件的大小,也就指定了DB可支持的最大collection数目,ns为namespace缩写。默认nssize为16MB。
如果重启MongoD并修改了nssize参数,这新nssize只会对新加入的DB生效,对以前已经存在的DB不生效,如果你想对已经存在的DB采用新的nssize,必须在加大nssize重启之后新建DB,然后把旧DB的collection 复制到新DB中。
namespace限制相关文档:http://docs.mongodb.org/manual/reference/limits/#Number-of-Namespaces
5、moveChunk因旧数据未删除而失败
错误日志:”moveChunk failed to engage TO-shard in the data transfer: can't accept new chunks because there are still 1 deletes from previous migration“。
意思是说,当前正要去接受新chunk 的shard正在删除上一次数据迁移出的数据,不能接受新Chunk,于是本次迁移失败。这种log里显示的是warning,但有时候会发现shard的删除持续了十几天都没完成,查看日志,可以发现同一个chunk的删除在不断重复执行,重启所有无法接受新chunk的shard可以解决这个问题。
参考:
http://stackoverflow.com/questions/26640861/movechunk-failed-to-engage-to-shard-in-the-data-transfer-cant-accept-new-chunk
如果采用了balancer自动均衡,那么可以加上_waitForDelete参数,如:
{ "_id" : "balancer", "activeWindow" : { "start" : "12:00", "stop" : "19:30" }, "stopped" : false, "_waitForDelete" : true }
,这样就不会因delete堆积而导致后续migrate失败,当然,需要考虑到这里的阻塞是否会影响到程序正常运转,在实践中慎重采用使用waitForDelete,因为发现加上它之后迁移性能非常差,可能出现卡住十几个小时的情况,外界拿住了被迁移chunk的游标句柄,这时候删除不能执行,阻塞了后续其它迁移操作。
游标被打开而导致被迁移数据无法及时删除时的日志:
2015-03-07T10:21:20.118+0800 [RangeDeleter] rangeDeleter waiting for open cursors in: cswuyg_test.cswuyg_test, min: { _id: -6665031702664277348 }, max: { _id: -6651575076051867067 }, elapsedSecs: 6131244, cursors: [ 220477635588 ]
这可能会卡住几十小时,甚至一直卡住,影响后续的moveChunk操作,导致数据不均衡。
解决方法还是:重启。
6、bson size不能超过16MB的限制
单个文档的BSON size不能超过16MB。find查询有时会遇到16MB的限制,譬如使用$in 查询的时候,in中的数组元素不能太多。对一些特殊的数据源做MapReduce,MapReduce中间会将数据组合为“KEY:[VALUE1、VALUE2]”这样的格式,当value特别多的时候,也可能会遇上16MB的限制。 限制无处不在,需要注意,”The issue is that the 16MB document limit applies to everything - documents you store, documents MapReduce tries to generate, documents aggregation tries to return, etc.
7、批量插入
批量插入可以减少数据往服务器的提交次数,提高性能,一般批量提交的BSON size不超过48MB,如果超过了,驱动程序自动修改为往mongos的多次提交。
8、安全写入介绍及其沿革
关键字:acknowledge、write concern。
在2012年11月之前,MongoDB驱动、shell客户端默认是不安全写入,也就是fire-and-forget,动作发出之后,不关心是否真的写入成功,如果这时候出现了_id重复、非UTF8字符等异常,客户端不会知道。在2012年11月之后,默认为安全写入,安全级别相当于参数w=1,客户端可以知道写入操作是否成功。如果代码使用Mongo或者Collection来连接数据库,则说明它是默认不安全写入的legacy代码,安全写入已经把连接数据库修改为MongoClient接口。
安全写入可以分为三个级别,
第一级是默认的安全写入,确认数据写入到内存中就返回(w=N属于这一级);
第二级是Journal save,数据在写入到DB磁盘文件之前,MongoDB会先把操作写入到Journal文件,这一级指的是确认写入了Journal文件就返回;
第三级是fysnc,所有数据刷写到到DB磁盘文件才返回。
一般第一级就足够了,第二级是为了保证在机器异常断电的情况下也不会丢失数据。安全写入要付出性能的代码:不安全写入的性能大概是默认安全写入的3倍。使用fync参数则性能更差,一般不使用。
如果是副本集(replica set),其w=N参数,N表示安全写入到多少个副本集才返回。
参考:
http://docs.mongodb.org/manual/release-notes/drivers-write-concern/
http://docs.mongodb.org/manual/core/write-concern/
http://blog.mongodirector.com/understanding-durability-write-safety-in-mongodb/
http://whyjava.wordpress.com/2011/12/08/how-mongodb-different-write-concern-values-affect-performance-on-a-single-node/
9、善用索引——可能跟你以为的不一样
使用组合索引的时候,如果有两组索引,在限量查询的情况下,可能跟常规的认识不同:
利用组合索引做的查询,在不同数量级下会有不同性能:
组合索引A: {"age": 1, "username": 1}
组合索引B: {"username": 1, "age": 1}
全量查询: db.user.find({"age": {"$gte": 21, "$lte": 30}}).sort({"username" :1}),使用索引A的性能优于索引B。
限量查询: db.user.find({"age": {"$gte": 21, "$lte": 30}}).sort({"username": 1}).limit(1000),使用索引B的性能优于索引A。
这两个查询在使用索引A的时候,是先根据age索引找到符合age的数据,然后再对这些结果做排序。使用索引B的时候,是遍历name,对应的数据判断age,然后得到的结果是name有序的。
优先使用sort key索引,在大多数应用上执行得很好。
参考:《MongoDB——The Definitive Guide 2nd Edition》page89
10、查询时索引位置的无顺序性
做find的时候,并不要求索引一定要在前面,
譬如:
db.test集合中对R有索引
db.test.find({R:"AA", "H": "BB"}).limit(100).explain()
db.test.find({"H":"BB", "R" : "AA"}).limit(100).explain()
这两个查找性能一样,它都会使用R索引。
11、使用组合索引做shard key可以大幅度提高集群性能
“固定值+增量值” 两字段做组合索引可以有效的实现分布式集群中的分散多热点写入、读取。以下为读书笔记:
在单个MongoDB实例上,最高效的写入是顺序写入,而MongoDB集群则要求写入能随机,以便平均分散到多个MongoDB实例。所以最高效的写入是有多个局部热点:在多个MongoDB实例之间是分散写入,在实例内部是顺序写入。 要实现这一点,我们采用组合索引。
例如:shardkey的第一部分是很粗糙的,可选集很少的字段,索引的第二部分是递增字段,当数据增加到一定程度时,会出现很多第一部分相同第二部分不同的chunk,数据只会在最后一个chunk里写入数据,当第一部分不同的chunk分散在多个shard上,就实现了多热点的写入。如果在一个shard上,不止一个chunk可以写入数据,那也就是说不止一个热点,当热点非常多的时候,也就等同于无热点的随机写