在項目中MongoDB的Map-Reduce功能做了許多統計任務,在重構代碼的時候修改了_id對象里面的屬性字段名稱,當用db.collection.update({$rename:{"_id.a":"_id.b"}})的時候提示mongodb $rename affecting _id not allowed錯誤消息。於是只能通過寫bash shell腳本來進行處理,之前學習MongoDB Manual時候里面有提到服務器端Javascript的特性但一直沒去詳細了解,今天正好有這個需求就查了下資料。
自MongoDB 2.4以后的版本采用V8引擎執行所有Javascript代碼,允許同時運行多個Javascript操作,在2.4版以前在執行Javascript的時候要求先獲取鎖,所以一次只能執行一個Javascript操作;MongoDB提供了對Javascript的完整支持,如在Node.js、MongoDB服務器端和mongo shell里面都提供非常好的支持。
MongoDB對下列服務器端操作支持執行Javascript代碼:
1)mapReduce或者相對應的mongo shell方法db.collection.mapReduce();
2)eval命令或者相對應的mongo shell方法db.eval()
3)$where操作符
4)在服務器端通過mongo shell實例運行.js文件
下面重點說說$where操作符以及在服務器端運行.js文件:
使用$where也應該注意以下幾點:
1.使用$where操作不能夠利用數據庫索引。
2.不能在$where Javascript表達式或函數里面執行數據庫寫操作。
3.為了避免對整個Collection文檔進行掃描並在每條記錄上執行Javascript代碼,應至少包含一個其他標准的查詢操作符如($gt,$in)對結果集先進行必要的過濾減少性能損耗。
那么什么情況下必須使用$where操作符呢?
看下面的Example Code:
db.myCollection.find( { $where: "this.credits == this.debits" } ); db.myCollection.find( { $where: "obj.credits == obj.debits" } ); db.myCollection.find( { $where: function() { return (this.credits == this.debits) } } ); db.myCollection.find( { $where: function() { return obj.credits == obj.debits; } } );
db.myCollection.find( { $where: function() { return this.cards.length>3; } } );
注意:這里this或者obj對象代表的是當前正在操作的記錄。
另外如果查詢僅僅由$where組成,這可以直接采用下面的用法:
db.myCollection.find( "this.credits == this.debits || this.credits > this.debits" ); db.myCollection.find( function() { return (this.credits == this.debits || this.credits > this.debits ) } );
標准的查詢操作符和$where一起使用:
db.myCollection.find( { active: true, $where: "this.credits - this.debits < 0" } ); db.myCollection.find( { active: true, $where: function() { return obj.credits - obj.debits < 0; } } );
注意:為了改善查詢性能MongoDB在$where執行前先執行所有其他非$where過濾條件來過濾結果集。在MongoDB 2.4及以后的版本的map-reduce、group和$where里面禁止訪問某些全局變量,如db等。
在Javascript文件里面寫mongo shell腳本:
我們能夠在Javascript文件里面寫mongo shell腳本來操縱在MongoDB里面的數據或執行管理操作。
從mongo shell或者Javascript文件里面實例化數據庫連接:
conn = new Mongo("[host][:port]"); db = conn.getDB("myDatabase"); //or
db = connect("localhost:27020/myDatabase");
同時MongoDB在Javascript里面提供了和在mongo shell里面的命令相對應的:如在mongo shell里面的show dbs,show databases,在Javascript里面可以通過db.adminCommand("listDatabases");獲得同樣的效果,又如在Javascript文件里面可以通過db=db.getSiblingDB("dbName");代替use dbName操作等等,詳細列表查看:http://docs.mongodb.org/manual/tutorial/write-scripts-for-the-mongo-shell/
執行服務器端Javascript的幾種方式:
1.在mongo shell里面通過load("myjstest.js")加載Javascript文件,然后在后面的命令里面可以調用該文件里面定義的函數了,就像普通的Javascript代碼一樣。在該js文件里面可以訪問此次mongo shell會話里面的全局變量,如db等。
Example Code:
function rename_id_field(taskId,mrId){ print("taskId is "+taskId+",mapReduceId is "+mrId); var collectionName = "test_collection"; var backupName = "test_collection_bak"
var count = db[collectionName].count({"value.taskId":taskId,"value.mapReduceId":mrId}); print("record size:"+count); var pageSize=10000; var pages = (count-1)/pageSize+1;
for(var p=1;p<=pages;p++){ var start = (p-1)*pageSize; print("Page:"+p+",start record:"+start); var cursor = db[cName].find({"value.taskId":taskId,"value.mapReduceId":mrId}).skip(start).limit(pageSize); while(cursor.hasNext()){ var rd = cursor.next(); rd._id.key=rd._id.name1; rd._id.tid=rd.value.taskId; delete rd._id.name1;delete rd.value.taskId; db[backupName].save(rd); } } }
將該文件保存在/opt/script/test.js,然后通過Linux命令行或者bash shell腳本的方式登陸到mongo shell,然后如下圖所示:
2.在mongo shell里面使用文本編輯器編輯Javascript代碼,首先需要在Linux系統里面指定環境變量如EDITOR=vim,然后如下所示:
MongoDB shell version: 2.2.0
> function f() {} > edit f function f() { print("this really works"); } > f() this really works
3.通過db.eval()方式在mongo shell里面執行Javascript代碼,如下所示:
db.eval( function(name, incAmount) { var doc = db.myCollection.findOne( { name : name } ); doc = doc || { name : name , num : 0 , total : 0 , avg : 0 }; doc.num++; doc.total += incAmount; doc.avg = doc.total / doc.num; db.myCollection.save( doc ); return doc; }, "eliot", 5 );
通過MongoDB提供的這幾種Javascript服務器端支持方式足以滿足我們對MongoDB數據庫進行復雜日常運行維護的管理工作。