mongodb基礎操作及進階理解

從2012年初開始粥喜,公司的一些核心產(chǎn)品準備開始陸續(xù)遷移到MongoDB上凸主,我們嘗試著從一個小產(chǎn)品開始使用,陸續(xù)將其他產(chǎn)品遷入额湘,到13年年底卿吐,公司產(chǎn)品在數(shù)據(jù)庫選擇上基本實現(xiàn)了NoSQL化旁舰,除了一些事務性要求較高(如支付)的模塊繼續(xù)停留在Mysql上,基本上現(xiàn)在大家都會偏向于使用MongoDB嗡官。就我個人而言箭窜,我覺得一些小項目(如后臺管理),或者需求變化極快的項目(現(xiàn)今的大部分中小移動互聯(lián)網(wǎng)產(chǎn)品)衍腥,如果對于并發(fā)要求不高绽快,沒有特別強的事務性,業(yè)務相對簡單紧阔,基本上就是一到兩人完成的小應用,mongodb應該是是這類應用的首選數(shù)據(jù)庫续担,我自己的體驗的理由如下:

  • 相對于MySQL等關(guān)系型數(shù)據(jù)庫擅耽,mongodb更為輕量,安裝物遇,使用乖仇,部署都輕便得多。

  • mongodb 的驅(qū)動寫得極為成熟询兴,天然的Bson數(shù)據(jù)結(jié)構(gòu)乃沙,使得存取數(shù)據(jù)都以Map結(jié)構(gòu)進行交互,數(shù)據(jù)接口非常方便诗舰,不需要額外進行數(shù)據(jù)轉(zhuǎn)換警儒,開發(fā)效率明顯提升。較為明顯的對比就是:如果使用MySQL眶根,往往需要使用一個第三方ORM框架進行DB層的操作蜀铲,以及Bean映射和數(shù)據(jù)轉(zhuǎn)換,mongodb完全不需要ORM属百,原生驅(qū)動已經(jīng)做得非常棒了记劝。

  • 相對松散的數(shù)據(jù)庫設計模式,使得它能更好的適應快速變化的需求族扰。當然這一點并不是說用mongodb不需要進行嚴謹?shù)臄?shù)據(jù)庫結(jié)構(gòu)設計了厌丑,只是說在需求變更涉及到庫表修改的時候,不像MySQL那么糾結(jié)要先去弄一下表結(jié)構(gòu)渔呵,我才敢部署應用怒竿。mongodb基本上沒有這個痛感。

  • mongodb 現(xiàn)在的最新穩(wěn)定版是2.4.8厘肮,至此愧口,它提供了相對完善的操作API,而且把Aggregation框架加入以后类茂,原來一直頭痛的各種統(tǒng)計操作也有了較好的解決方案耍属,現(xiàn)在可以比較放心的說托嚣,MySQL能完成的幾乎所有事情,mongodb都能完成厚骗。

  • mongodb的文檔現(xiàn)在真的好的令人發(fā)指啊示启,應該可以算是業(yè)界文檔的模范了。

這篇文章主要想介紹一下mongodb的一些基本常用的操作领舰,順便將一些工作中的處理和理解也提出來夫嗓,希望能稱得上是一篇進階之作。

1. insert,插入數(shù)據(jù)

insert操作比較簡單冲秽,mongodb提供了insert, save 方法進行數(shù)據(jù)插入操作舍咖。
insert就是普通插入,如果待插入的數(shù)據(jù)中未含有key:'_d'锉桑,mongodb則會自動生成一個類型為ObjectId排霉,key為'_id'的數(shù)據(jù)作為該條記錄的主鍵,如果已經(jīng)含有民轴,則只校驗一下'_id'是否存在于集合中攻柠,未存在則會插入成功,否則會返回一個錯誤后裸。
sava 方法會根據(jù)待處理的數(shù)據(jù)中是否含有key:'_id'進行處理瑰钮,沒有包含則插入數(shù)據(jù),包含則根據(jù)這個_id更新原有數(shù)據(jù)微驶。
另外浪谴,insert方法還可以進行批量操作,只要將需要插入的數(shù)據(jù)按照數(shù)組格式組裝傳入即可祈搜。
基本語法如下:

//
db.collection.insert({key:value});
db.collection.insert([{key:value},{key:value}...]);
db.collection.save({key:value});

2. remove较店,刪除數(shù)據(jù)

remove操作也很簡單,只需要把刪除條件傳入即可容燕。
基本語法:

db.collection.remove({key:value});

如果沒有傳入任何刪除條件梁呈,則會刪除整個集合。

3. update蘸秘,更新數(shù)據(jù)

update稍微復雜一些官卡,我們在開發(fā)中碰到的關(guān)于更新的操作大概有以下三種情況:

  • 普通更新操作(update.$set|$unset)。
  • 原子更新操作(update.$inc)醋虏。
  • 阻塞查詢更新操作(findAndModify.$set|$inc)寻咒。
  • 數(shù)組相關(guān)更新操作($push|$pull|$addToSet|$pop 等)。

3.1 普通更新操作

首先來說一下update的基本語法:

db.collection.update( <query>, <update>, <upsert>, <multi> )

query:更新的查詢條件.
update: 更新的數(shù)據(jù).
upsert: 當查詢條件沒有找到數(shù)據(jù)時是否插入,默認false.
multi:是否更新多條颈嚼,默認false.
這里需要強調(diào)一下的是對于選項【update】的處理毛秘,如果是更新全文檔,則無需特別處理;如果只更新文檔中的幾個字段叫挟,則需要加"$set"進行處理艰匙,不然會將文檔覆蓋掉,在寫數(shù)據(jù)處理腳本的時候要特別注意這些地方抹恳。這里提供一個對于【普通更新操作】的示例:

db.Student.update(
        {_id:ObjectId("52e8fce17ee72c8860511af6")},
        {"$set":{"name":"jay","status":1}},
        false,
        true
    )

3.2 原子更新操作

mongodb對于自增長的處理是通過$inc來實現(xiàn)的员凝,自增長的過程是原子性的。示例如下:

db.Student.update(
        {_id:ObjectId("52e8fce17ee72c8860511af6")},
        {"$inc":{"age":3}},
        false,
        true
    )

上面這段代碼將Student中的一條記錄的age字段自增長了3奋献。
如果在一個update操作中健霹,我既有更新部分數(shù)據(jù)的需求,又希望對某個字段進行自增長操作,還希望刪除某個字段瓶蚂,這里的處理就很簡單了:

db.Student.update(
        {_id:ObjectId("52e8fce17ee72c8860511af6")},
        {
            "$set":{"name":"jay","status":1}
            ,"$inc":{"age":3},
            ,"$unset":{"sex":1}
        },
        false,
        true
    )

3.3 阻塞查詢更新操作

這里需要提一下mongodb的鎖機制了糖埋。

3.3.1 MongoDB 使用的鎖

MongoDB 使用的是“readers-writer”鎖, 可以支持并發(fā)但有很大的局限性窃这,當一個讀鎖存在,許多讀操作可以使用這把鎖阶捆,然而, 當一個寫鎖的存在,一個單一的寫操作會 exclusively 持有該鎖钦听,同時其它讀,寫操作不能使用共享這個鎖倍奢;舉個例子朴上,假設一個集合里有 10 個文檔,多個 update 操作不能并發(fā)在這個集合上卒煞,即使是更新不同的文檔痪宰。

3.3.2鎖的粒度

在2.2版本以前,mongod只有全局鎖畔裕;在2.2版本開始衣撬,大部分讀寫操作只鎖一個庫,相對之前版本扮饶,這個粒度已經(jīng)下降具练,例如如果一個 mongod 實例上有5個庫,如果只對一個庫中的一個集合執(zhí)行寫操作甜无,那么在寫操作過程中扛点,這個庫被鎖;而其它5個庫不影響岂丘。相比RDBMS來說陵究,這個粒度已經(jīng)算很大了!

可以看出奥帘,mongodb這種鎖機制設計得不是很合理铜邮,數(shù)據(jù)到了一定數(shù)量級比較容易出現(xiàn)性能問題,所以要特別注意【更新】和【查詢】操作。

我現(xiàn)在的需求是松蒜,要在mongodb中獲取自增長的Integer類型的主鍵扔茅。利用findAndModify以及mongodb的鎖機制可以實現(xiàn)這一需求。findAndModify既是read的操作牍鞠,又是write的操作咖摹,在執(zhí)行findAndModify時,mongodb會對集合進行writer加鎖难述,其他線程不能進行write操作萤晴,操作完畢以后,它同時返回操作后的最新結(jié)果胁后,保證read的準確性店读。這樣就保證了每一次只能執(zhí)行write and read in document的事情。
我們在實踐中的設計是這么做的:

  • 設計一個Collection攀芯,集合名為AutoIds.插入一條數(shù)據(jù):{_id:1}.
  • 實現(xiàn)生成自增長并返回主鍵邏輯,這里用的是java驅(qū)動:
 public Integer getNextId(String fieldName) {
        DBCollection autoIdsColl = db.getAutoIdsCollection();
        // _id=1, 確定預先插入的唯一一條記錄
        DBObject query = new BasicDBObject("_id", 1);
        // 過濾一下查詢的 field
        DBObject fields ={_id:1, fieldName:1};
        // 排序
        DBObject sort = new BasicDBObject("_id", 1);
        // 定義每次自增長幅度為1
        update = new BasicDBObject("$inc", new BasicDBObject(fieldName, 1));
        // 更新并返回
        DBObject obj = autoIdsColl.findAndModify(query, fields, sort, false, update, true, true);
        // 返回此次更新的Id值
        Integer id = (Integer) obj.get(fieldName);
        return id;
    }
  • 由上一步可知,AutoIds只有一條記錄屯断,理論上可以無限橫向擴展,為多個表維護ID侣诺,只需要傳遞不同的ID的key作為getNextId的參數(shù)即可殖演。

相對于關(guān)系型數(shù)據(jù)庫,mongodb需要繞這么一大圈確實有點說不過去年鸳,而且由于鎖機制的欠缺趴久,性能還差了一大截,不過在實際業(yè)務中搔确,mongodb自帶的ObjectId作為主鍵其實能解決大部分問題彼棍,所以也還算能接受。

3.4 數(shù)組更新操作

數(shù)組相關(guān)的更新操作在大部分情況下和普通更新操作沒有啥特別大的區(qū)別膳算,無非就是加了幾個操作符座硕。但是也有一些棘手的操作,由于不常用涕蜂,每次弄的時候總是要回過頭來翻文檔华匾,所以我這里單獨提一下。

3.4.1 添加一個子項到數(shù)組中
db.Student.update(
        {_id:ObjectId("52e8fce17ee72c8860511af6")},
        {
            "$push":{"courses":{"name":"Math","code":"001"}}
        },
        false,
        true
    )

3.4.2 添加多個子項到數(shù)組中
db.Student.update(
        {_id:ObjectId("52e8fce17ee72c8860511af6")},
        {
            "$addToSet":{"courses":
                           {"$each":[ {"name":"Math","code":"001"}
                                      ,{"name":"English","code":"002"}
                                    ]
                            }
                        }
        },
        false,
        true
    )

這里的$addToSet會保證帶插入的數(shù)組中相同子項只會存在一個机隙,重復的子項也只會插入一次瘦真。如果業(yè)務需求沒有這么嚴謹,也可以用$push代替黍瞧。

3.4.2 移除指定子項
db.Student.update(
        {_id:ObjectId("52e8fce17ee72c8860511af6")},
        {
            "$pull":{"courses":{"name":"Math","code":"001"}}
        },
        false,
        true
    )

3.4.2 更新數(shù)組子項中的某個field

這里要借用占位符 $ 來完成诸尽。 先看示例:

db.Student.update(
        {
            _id:ObjectId("52e8fce17ee72c8860511af6")
            ,"courses.code":"001"
        },
        {
            "$set":{"courses.$.name":"MATH"}
        },
        false,
        true
    )

這個語句稍微解釋一下:
a) 對于更新的查詢條件,務必加 【"courses.code":"001"】這一項印颤,這樣才能定位到數(shù)組中的具體項您机。這里我之前有一個疑惑,就是加不加【"courses.code":"001"】都能查到同一條記錄,為啥一定要加呢际看,主要是為了定位數(shù)組中的子項咸产。

b) 有了 a)的解釋,【"$set":{"courses.$.name":"MATH"}】中的 "$" 的意思就很好理解了仲闽,它就是用來定位數(shù)組子項當前項的脑溢,這兩個寫法缺一不可。

占位符$的使用在涉及到數(shù)組子項的查詢也需要用到赖欣,后面的章節(jié)會說屑彻。

4. query 查詢

查詢操作其實比較簡單了,mongodb提供了大量的操作符來做這個事情顶吮。之前我也說了mongodb的文檔做得非常好社牲,所以一些普通查詢操作,直接翻文檔吧悴了,里面有語法搏恤,實例,非常棒湃交。 鏈接
這里我就不準備把文檔翻譯一遍了熟空,我寫一下在使用過程中一些必要但是稍微繞了一下的處理。

4.1 優(yōu)雅實現(xiàn) between...and

db.Student.find({
    "time":
        {
            "$gt":start,
            "$lt":end
        }
})

這個結(jié)構(gòu)對我的啟發(fā)就是:我個人認為 $and 基本上是多余的搞莺。
之前用$and實現(xiàn)的方式:

db.Student.find({
    "$and":[
        {"time":{"$gt":start}}
        ,{"time":{"$lt":end}}
    ]
})

這樣一對比痛阻,后者真的笨重而且多余。所以仔細想想腮敌,似乎所有的查詢條件都不需要通過$and這樣通過數(shù)組來實現(xiàn)呀,Map結(jié)構(gòu)本來就支持多鍵存放的嘛俏扩。

4.2 ‘like’ 的新樣子

db.Student.find({
    "name":
    {
        "$regex":"/abc[dD]{1}/"
    }
})

正則表達式來實現(xiàn)like的功能糜工,而且更為強大,唯一需要考慮的就是效率問題录淡。這里順帶也把全文搜索也牽出來了捌木,范圍太大了,以后單獨講嫉戚。

4.3 數(shù)組子項的查詢刨裆,中規(guī)中矩的$elemMatch,還是有更方便的寫法?

示例:

db.Student.find({
      "courses":{
            "$elemMatch":{"code":"001"}
      }
    });

偶然發(fā)現(xiàn)還有一個超級簡單的寫法:

db.Student.find({
      "courses.code":"001"
    });

這里很容易引起混淆彬檀,到底Student的數(shù)據(jù)結(jié)構(gòu)是怎么樣的帆啃?【courses】這個字段類型是Map子文檔(map)還是數(shù)組子文檔(List)呢? 實際上只要它是二者中的任何一種窍帝,都可以用上面的寫法查詢出來努潘。

4.4 根據(jù)數(shù)組子項查詢,希望只返回查詢到的數(shù)組子項,應該怎么寫疯坤?

db.students.find( 
            {_id:ObjectId("6718703038737487484498")
              , "courses.code": "001" 
            },
            { "courses.$": 1 })

這里find方法使用了第二個參數(shù)报慕,【courses.$】又看到了熟悉的占位符了,這里的作用還是一樣压怠,就是定位到query參數(shù)中查詢到的子項眠冈,并只返回這個子項。

其實查詢操作還有很多地方?jīng)]有說到菌瘫,例如基于位置的查詢蜗顽,全文搜索等。但是只要了解了本文所說的篇幅突梦,日常開發(fā)中應該大部分也夠了诫舅。
查詢操作避不開的話題就是效率問題,我會單獨寫一篇這方面的文章宫患,從索引刊懈,鎖機制等探討一下在mongodb中查詢和更新等操作需要注意的問題。

綜上娃闲,基本的操作都說了一下虚汛,我覺得還是多翻文檔,用多了自然就熟了皇帮。

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末卷哩,一起剝皮案震驚了整個濱河市,隨后出現(xiàn)的幾起案子属拾,更是在濱河造成了極大的恐慌将谊,老刑警劉巖,帶你破解...
    沈念sama閱讀 216,591評論 6 501
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件渐白,死亡現(xiàn)場離奇詭異尊浓,居然都是意外死亡,警方通過查閱死者的電腦和手機纯衍,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 92,448評論 3 392
  • 文/潘曉璐 我一進店門栋齿,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人襟诸,你說我怎么就攤上這事瓦堵。” “怎么了歌亲?”我有些...
    開封第一講書人閱讀 162,823評論 0 353
  • 文/不壞的土叔 我叫張陵菇用,是天一觀的道長。 經(jīng)常有香客問我陷揪,道長刨疼,這世上最難降的妖魔是什么泉唁? 我笑而不...
    開封第一講書人閱讀 58,204評論 1 292
  • 正文 為了忘掉前任,我火速辦了婚禮揩慕,結(jié)果婚禮上亭畜,老公的妹妹穿的比我還像新娘。我一直安慰自己迎卤,他們只是感情好拴鸵,可當我...
    茶點故事閱讀 67,228評論 6 388
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著蜗搔,像睡著了一般劲藐。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上樟凄,一...
    開封第一講書人閱讀 51,190評論 1 299
  • 那天聘芜,我揣著相機與錄音,去河邊找鬼缝龄。 笑死汰现,一個胖子當著我的面吹牛,可吹牛的內(nèi)容都是我干的叔壤。 我是一名探鬼主播瞎饲,決...
    沈念sama閱讀 40,078評論 3 418
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼炼绘!你這毒婦竟也來了嗅战?” 一聲冷哼從身側(cè)響起,我...
    開封第一講書人閱讀 38,923評論 0 274
  • 序言:老撾萬榮一對情侶失蹤俺亮,失蹤者是張志新(化名)和其女友劉穎驮捍,沒想到半個月后,有當?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體脚曾,經(jīng)...
    沈念sama閱讀 45,334評論 1 310
  • 正文 獨居荒郊野嶺守林人離奇死亡东且,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 37,550評論 2 333
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發(fā)現(xiàn)自己被綠了斟珊。 大學時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點故事閱讀 39,727評論 1 348
  • 序言:一個原本活蹦亂跳的男人離奇死亡富纸,死狀恐怖囤踩,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情晓褪,我是刑警寧澤堵漱,帶...
    沈念sama閱讀 35,428評論 5 343
  • 正文 年R本政府宣布,位于F島的核電站涣仿,受9級特大地震影響勤庐,放射性物質(zhì)發(fā)生泄漏示惊。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點故事閱讀 41,022評論 3 326
  • 文/蒙蒙 一愉镰、第九天 我趴在偏房一處隱蔽的房頂上張望米罚。 院中可真熱鬧,春花似錦丈探、人聲如沸录择。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,672評論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽隘竭。三九已至,卻和暖如春讼渊,著一層夾襖步出監(jiān)牢的瞬間动看,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 32,826評論 1 269
  • 我被黑心中介騙來泰國打工爪幻, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留菱皆,地道東北人。 一個月前我還...
    沈念sama閱讀 47,734評論 2 368
  • 正文 我出身青樓笔咽,卻偏偏與公主長得像搔预,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子叶组,可洞房花燭夜當晚...
    茶點故事閱讀 44,619評論 2 354

推薦閱讀更多精彩內(nèi)容