標籤:style blog http color io os 使用 ar strong
太久沒動這裡,目前人生處於一個新的開始。這次部落格的內容很久前就想更新上來,但是一直沒找到合適的時間點(哈哈,其實就是懶),主要內容集中在使用Mongodb時的一些隱形MapReduce問題:
1、Reduce時的計數問題
2、Reduce時的提取資料問題
另外,補充一個小tips:mongoDB中建立的索引,優先使用固定的,而不要使用範圍。
一、MapReduce時的計數問題
這個問題主要出現在使用“+1”的思路去計算累計次數時。如果在Map後的某一類中,記錄量過大,就會導致計數失敗。
具體示範如下:
未經處理資料(有400條一樣的存在資料庫results表中):{ "grade" : 1, "name" : "lekko", "score" : 95 }
進行MapReduce:
1 db.runCommand({ mapreduce: "results", 2 map : function Map() { 3 emit( 4 {grade:this.grade}, 5 {recnum:1,score:this.score} 6 ); 7 }, 8 reduce : function Reduce(key, values) { 9 var reduced = {recnum:0,score:0};10 values.forEach(function(val){11 reduced.score += val.score;12 ++reduced.recnum;13 });14 return reduced;15 },16 finalize : function Finalize(key, reduced) { 17 return reduced;18 },19 out : { inline : 1 }20 });
滿懷希望地以為value.recnum會輸出400,結果卻是101!而value.scorce卻是輸出的正確的:38000(95*400)。本人在這疑惑了好久,並且通過更改reduce函數: function Reduce(key, values) { return {test:values}; } ,探索資料是這樣的:
在原本Reduce函數中的forEach只遍曆了第一層的資料,即101個,所以++操作也只做了101次!
經過思考,導致問題的原因關鍵就在於MapReduce中emit後的Bosn的資料格式,一個大於100的Array,會被拆分儲存,變成了非線性鏈表結構,
那麼,分數相加卻能正確,可以大膽地推測:“reduced.score += val.score;” 語句可以智能地找到所有子結點的score並相加!
最後,這裡給出計數的替代方案,修改Reduce的++,改用+=操作:
1 function Reduce(key, values) { ;2 var reduced = {recnum:0,score:0};3 values.forEach(function(val){4 reduced.score += val.score;5 reduced.recnum += val.recnum;6 });7 return reduced;8 }
二、在Reduce中把資料提取出來組成Array
這個問題產生的原因與上面的相似,也是由於emit後的資料在reduce時是非線性(有層次關係),所以提取資料欄位時也會產生問題,為了測試,往上面所說的表中再插入3條資料:
{ "grade" : 1, "name" : "monkey", "score" : 95 }, { "grade" : 2, "name" : "sudan", "score" : 95 }, { "grade" : 2, "name" : "xiaoyan", "score" : 95 }
編寫提取出各個grade的所有人名(不重複)列表:
1 db.runCommand({ mapreduce: "results", 2 map : function Map() { 3 emit( 4 {grade:this.grade}, 5 {name:this.name} 6 ); 7 }, 8 reduce : function Reduce(key, values) { 9 var reduced = {names:[]};10 values.forEach(function(val) {11 var isExist = false;12 for(var i = 0; i<reduced.names.length; i++) {13 var cur = reduced.names[i];14 if(cur==val.name){15 isExist = true;16 break;17 }18 }19 if(!isExist)20 reduced.names.push(val.name);21 });22 return reduced;23 },24 finalize : function Finalize(key, reduced) {25 return reduced;26 },27 out : { inline : 1 }28 });
返回結果為:
1 { "_id" : {"grade" : 1},2 "value" :{ "names" : [null,"lekko"]}3 },4 { "_id" : {"grade" : 2},5 "value" :{ "names" : ["xiaoyan","sudan"]}6 }
新插入的grade=2的兩條資料正常了,但grade=1的monkey卻不見了!採用問題一的思維方式,肯定也是在Reduce時遍曆到一個數組對象,其name值為空白,也給添加進來了,monkey對象根本就沒有訪問到。
解決這一問題的方法是,拋棄MapReduce,改用Group:
1 db.results.group({ 2 key : {"grade":true}, 3 initial : {names:[]}, 4 reduce : function Reduce(val, out) { 5 var isExist = false; 6 for(var i = 0; i<out.names.length; i++) { 7 var cur = out.names[i]; 8 if(cur==val.name){ 9 isExist = true;10 break;11 }12 }13 if(!isExist)14 out.names.push(val.name); 15 }, 16 finalize : function Finalize(out) {17 return out;18 }});
這樣,便可正常取到grade=1時的name非重複集合!雖說MapReduce比Group要強大,速度也要快很多,但像這種要從大量項(超過100條)中提取資料,就有很大風險了。所以,使用MapReduce時,盡量只用到累加、累減、累乘等基本操作,不要去用++、push、delete等可能會產生風險的操作!
三、補充幾個小Tips
1、使用Group或MapReduce時,如果一個分類只有一個元素,那麼Reduce函數將不會執行,但Finalize函數還是會執行的。這時你要在Finalize函數中考慮一個元素與多個元素返回結果的一致性(比如,你把問題二中插入一個grade=3的資料看看,執行返回的grade=3時還有names集合嗎?)。
2、尋找範圍時的索引效率,如果查詢的是一個值的範圍,它索引的優先順序是很低的。比如一個表test,有海量元素,欄位有‘committime‘、‘author‘,建立了兩個索引:author_1、committime:-1,author:1,下面的測試證明了效率:
db.test.find({‘committime‘:{‘$gt‘:910713600000,‘$lte‘:1410192000000},‘author‘:‘lekko‘}).hint({committime:-1,author:1}).explain() "millis" : 49163
db.test.find({‘committime‘:{‘$gt‘:910713600000,‘$lte‘:1410192000000},‘author‘:‘lekko‘}).explain() author_1 "millis" : 2641
轉載請註明原址:http://www.cnblogs.com/lekko/p/3963418.html
在MongoDB的MapReduce上踩過的坑