mongodb的存储结构是灵活可变的,但是,并不意味着我们就肆意地使用不规则的文档结构。不规则的文档结构对于开发和后期的维护都是一个灾难。所以,还是要有一个约定的格式。
但是,由于前期设计的不周详和其他种种原因,数据库文档结构在开发过程的中修改总是难以避免的,应该尽量减少这种修改。但是,到了必须改的时候还是得改:
1 { 2 "_id" : ObjectId("54a1f775e4b03dad3af55c3c"), 3 "myId" : "54a0b115e4b00712935204ba", 4 "name" : "action", 5 "key" : "m_0", 6 "index" : 0, 7 "createTime" : ISODate("2014-12-30T00:53:09.483Z"), 8 "subMs" : [ 9 { 10 "_id" : null, 11 "rm" : { 12 "mt" : "TEXT", 13 "content" : "撒东西" 14 }, 15 "name" : "地说道", 16 "key" : "menu_0_0", 17 "type" : "CLICK" 18 } 19 ] 20 } 21 { 22 "_id" : ObjectId("54b87996e4b04b29b92a71b1"), 23 "myId" : "54b5e8cce4b045d4121f5d63", 24 "rm" : { 25 "msgType" : "URL", 26 "url" : "http://www.abc.com" 27 }, 28 "name" : "usercenter", 29 "key" : "user-center", 30 "type" : "VIEW", 31 "index" : 0, 32 "createTime" : ISODate("2015-01-16T02:38:14.643Z") 33 }] 34 35 }
有很多这样类型的文档。 这个文档中的subMs字段中内嵌有若干个文档。要做的事情就是把subMs中的若干个文档脱离出来成为一个独立的文档,并且使新拆出来的文档保留原父文档的id。
脚本如下:
1 var mList = db.m.find(); 2 var mLength = mList.length; 3 var mArray = new Array(); 4 // 这一步比较关键,因为find 方法会返回一个游标, 5 // 如果不先关闭,或者将此游标用完,接下来在继续往 6 // mongodb中插入数据会导致该游标发生混乱。 7 // 所以此处未往mongodb中修改前先把游标用完。 8 while(mList.hasNext()) { 9 mArray.push(mList.next()); 10 } 11 for(var i = 0; i < mLength; i ++) { 12 var mItem = mArray.pop(); 13 var sMList = mItem.subMs; 14 mItem.subMs = new Array(); 15 if(sMList == 0 || sMList.slength == 0) { 16 continue; 17 } 18 var sMLength = sMList.slength; 19 for(var j = 0; j < sMLength; j ++) { 20 var sMItem = sMList.pop(); 21 sMItem.pid = mItem._id.str; 22 sMItem.index = j; 23 sMItem.subMs = new Array(); 24 sMItem._id = undefined; 25 db.m.insert(sMItem); 26 } 27 db.m.save(mItem); 28 }
第一次写起来还是有点吃力,
第一,是由于js的非常不熟悉。翻了好几次w3school才看出了点东西。
第二,是对mongodb查询游标的理解。
着重说说,mongodb 的游标:
游标:在mongodb查询中,返回一个可迭代的对象,这个对象就叫做游标。这个对象保存着所有的查询结果集。
游标的行为:
1. 在mongo shell 中,游标默认显示前20个结果集,敲入 it 翻页。显示下一个 20个。
关闭mongodb 的游标:游标在mongodb中如果没有被迭代到最后,那么它在10分钟后自动关闭。或者是被迭代到最后。
修改mongodb游标的自动过期时间方法为:
var myCursor = db.m.find().addOption(DBQuery.Option.noTimeout);
// DBQuery 这个对象中也有挺多东西,有空翻来看看
由于游标在生存周期中不是隔离的,游标存活期间在对一个文档的写操作可能会时该文档的返回次数超过一次。当这个文档被改变之后,这就是我在写上面脚本的时候一开始缺少了先把游标用完,就对原文档修改,造成错误。
所以,我上面的写法存在问题,当数据量很大的时候,把游标中所有的东西加载到内存后会把内存撑爆的。。。
解决办法:使用游标快照: (P707)
mongodb的游标会对一个文档返回超过一次,在一些特殊的情况下,那么这个时候就可以使用快照方法 snapshot().
snapshot()贯穿在_id字段上的索引,保证查询返回的每个文档出现的_id值不超过一次。(遵循_id值)
快照方法不保证数据返回时会返回单一时刻。 也不保证对插入和删除操作的隔离。
警告:
1.不能再分片集合中使用快照方法
2.不能在快照方法的同时使用 sort方法或者hint()方法。
作为一个选择,如果在集合中存在有一个或多个字段是永远不会改变的,那么可以在该字段上创建唯一索引,来得到和使用库依照方法类似的集合。查询时使用hint方法强制查询时使用这个唯一索引。
第三,mongo shell, 有个好东西, 当敲一个命令的时候,不敲后边跟上的括号,就能显示该命令底下将会执行的js方法。
比如以下命令:
> db.m.find 回车后会显示对应的方法。
另外,有个挺好用的mongodb客户端:robomongo 值得推荐。
以上参考了mongodb的官方文档。写的很给力的一个文档。非常值得一看,居家备用查询也挺好。