mongodb

不知不覺~漫長的鐵人賽就進入了尾聲,當初會參加鐵人賽也只是因為,沒參加過 ~ 來試試看,而且也剛好我今年的時間比較多點兒,話說回來,為什麼我會選MongoDB來當題目呢?事實上也只是因為我自已無聊在做的專案,有把mongoDB拿來用,所以就想說認真的來研究一下mongoDB ~ 我們簡單的總結一下我們這三十天學了那些東西。 首先最基本的一定是一個資料庫的CRUD,這階段就像玩天堂時的說話島一樣,你要打打哥布林。 30-3之新手村CRUD—新增 30-4之新手村CRUD—新增之Bulk與新增效能測試 30-5之新手村CRUD—更新 30-6之新手村CRUD—更新之陣列欄位 30-7之新手村CRUD—刪除 30-8之新手村CRUD—搜尋之find與搜尋操作符號 30-9之新手村CRUD—搜尋之陣列欄位與regex 30-10之新手村CRUD—搜尋之Cursor運用與搜尋原理 然後在基礎的新手村畢業以後,你就可以坐船前往大陸,不過下船的地方在那我有點忘了。 接下來我們要學習的事情就是,要如何的使我們搜尋速度更快速。 30-11之索引(1)—索引的哩哩扣扣 30-12之索引(2)—複合索引的坑 30-13之索引(3)—比較特別的索引使用 在我們了解了如何將搜尋速度提升更快後,我們就可以來研究如何使用mongodb來進行資料分析,這個階段就像是龍之谷吧……年代有點久遠有點快忘了。 30-14之聚合(1)—Aggregate Framework的哩哩扣扣 30-15之聚合(2)—Pipeline武器庫 30-16之聚合(3)—潮潮的MapReduce 可是分析完後,我們發覺有些地方效能還不是不太好,明明索引那些都處理好囉 ? 這時我們只能往架構方面來尋找問題囉。 30-17之MongoDB的設計—正規與反正規化的戰爭 在以上的東西都已經學習的差不多時,這時我們就要來驗證一下,我們是否真的有學習進腦袋裡,這時最簡單的驗證方法,就是自已想個題目,然後從0 → 1 自已建立看看。順到一題0 → 1這本書真的不錯看。 30-18之運用研究—PO文模擬情境(1) 30-19之運用研究—PO文模擬情境(2) 30-20之運用研究—PO文情境模擬(3) 在驗證完以上的東西都學習會後,我們可以往分散式的東西進行學習囉,這邊應該就是傲慢之塔的等級囉。 30-21之MongoDB的副本集 replica set(1) 30-22之MongoDB的副本集 replica set(2)—使用Docker建立MongoDB Cluster 30-23之分片Sharding(1)—Hello Sharding 30-24之分片Sharding(2)—Chunk的札事 30-25之分片Sharding(3)—片鍵的選擇 然後接下來的最後一部份也是驗證你上面的東西有沒有學會。 30-26之運用研究—股價應用模擬(1) 30-27之運用研究—股價應用模擬(2) 30-28之運用研究—股價應用模擬(3) 事實上到這邊應該就可以結束了,但我事實上有忘記一個主題,所以補充在最後面。 30-29之補充—忘了講的事務操作 最後忘了講幾句感言的話,事實上我很感謝上天,還能給予我可以參加30天鐵人賽的腦袋與體力,2016年應該是我目前的人生轉哲最大的年度,我生了一場重病,我得的病就是你們腦袋中最不想得的病排行板前三名, ~ 啊喲啊喲 ~ 小的才2開頭而以 ~ 啊喲啊喲 ~ 得這病是真的失去不少東西,而且治療過程,有時後我會想,似乎上天堂好像會輕鬆點兒,上一句只是完笑,得這鬼病也只有一條路,面對現實就對囉~
本篇文章是用來補充一下,前面忘了講的觀念,記得在第一篇時,我們有提過下面這句話。 MongoDB 不支持事務操作 但事實上這段話有很多觀念要來說明說明,不然很難讓人了解事務操作是啥,所以我們這篇要用來補充一下這個主題。 ~ 事務操作是啥鬼 ~ 咱們首先先來了解一下,事務是啥?根據wiki的定義。 資料庫事務是資料庫管理系統執行過程中的一個邏輯單位,由一個有限的資料庫操作序列構成。 這邊用白話文來簡單說明一下,事實操作你可以把他想成一個工作流程,例如煮菜,你首先要先洗菜、切菜、丟到鍋子、加調味料,『煮菜』這名詞就是一個事務,它裡面包含了剛剛說明的流程。 我們轉回的在資料庫中的事務,假設我們是個證券商,我們收到使用者的下單通知,那我們資料庫會著麼進行? 我們下面來試試列出該事務操作過程。其中我們有兩個資料表accounts為使用者的帳戶資料、第二個為orders下單資料,呃對了先不管交割日這鬼,也就是付錢日。 首先我們會先在orders新增一筆訂單。 再到accounts針對該使用者的帳戶進行扣款。 那如果發生錯誤時,事務會著麼處理? 根據以上的例子,我們拿來繼續使用,假設我們在第二個步驟,準備要扣款時,系統突然gg了,那要著麼樣?在一些資料庫中,當整個事務提交給資料庫時,它會保證這整個事務要嘛全部完成,要嘛全部沒完成。 也就是說,如果我們第二個步驟掛掉時,我們一開始在orders新增的一筆訂單會取消,會保持整個事務的完整性,不會只完成一半。 最後這邊我們來看一下事務操作的四個特性ACID,來腦補一下,以下內容為wiki,並且自已寫寫說明。 原子性(Atomicity) : 要麼全執行、要麼全取消,沒得商量。 一致性(Consistency): 這個是指在事務開始與結束後,資料庫的完整性約束沒有被破壞。 隔離性(Isolation): 多個事務執行時,任一個事務不會影響到其它的事務。 持久性(Durability): 代表即時停電或啥,事務一旦提交後,則持久化保存在資料庫中。 ~ MongoDB 不支援事務 ~ 對mongodb不支援事務,但它還是有支援一些符合各別特性的操作,總共有三個。 1. 在單個 document 上有提供原子性操作 findAndModify mongodb有提供單個document,操作,也就是說如果你要針對該document進行更新,要麼全部更新完成,不然就全部不更新,我們簡單用個範例來說明如何設計成,符合原子性的功能。 我們把上面的例子拿下來用。 假設我們是個證券商,我們收到使用者的下單通知,那我們資料庫會著麼進行? 我們下面來試試列出該事務操作過程。其中我們有兩個資料表accounts為使用者的帳戶資料、第二個為orders下單資料,呃對了先不管交割日這鬼,也就是付錢日。 但注意一點,如果我們是建立將accounts與orders分成兩個collection來建立,那我們就沒辦法使用mongodb所提供的原子性操作,因為就變為多document的操作。 所以我們需要將它修改為都存放在同一個collection,沒錯也就是進行反正規化,資料大概會變成這樣。 { "user" : "mark" , balance : 10000 , orders : [ { "id" : 1 , "total" : 1000 , "date" : "20160101" }, { "id" : 2 , "total" : 2000 , "date" : "20160103"} ] } 然後我們進行交易時,我們需要先檢查balance確定是否有足的錢,然後在新增一筆下單到orders欄位中,最後才修改balance,而我們這時需要用到findAndModify ,它可以確保這筆交易的,在確定完balance後,不會有其它線程來更新它的balance。
上一篇研究簡單的說明完,股價分析的運用操作後,接下來我們這篇文章將要說明一些程式交易的東西,不過雖然說是程式交易,但事實上也只是簡單的計算出技術分析指標然後產生出買賣時間點,要說是程式交易好像也不太算兒……。 ~ 二哈的需求分析 ~ 今天咱們的二哈和我說,啊鳴~ 最近我想搞搞程式交易 ~ 幫我一下,然後我問他你想做啥,他竟然回,我也不知道也 ~ 我只是聽說那很潮、很容易賺錢 ~ 幫我咱 ~咱們是好哥吧,然後我一直在笑他你傻了啊,最後他就用出這種表情。 雖然很想和他說~你何不食屎忽 ~ 但想到要愛護動物就想是幫他想一下。 回到正題,說到程式交易我們先看一下智庫的定義。  程式交易在英文中叫做Program Trading, 就是將自己的金融操作方式,用很明確的方式去定義和描述,且遵守紀律的按照所設定的規則去執行交易。 上面只是定義,不過我簡單的說明一下我的認知,就是『寫個策略計算出買賣點,然後叫電腦乖乖的進行交易』。 網路上以及論文都有提供一些策略,你可以自已去試試看,不過會不會賺錢小的我就不知道了,順到幫我老師打廣告一下,如果對金融應用感興趣的可以看看他寫的書,傳送門在此。 好再一次回來正題,那我們在這邊就簡單的講幾個策略……真的很簡單,因為我模擬的資料只有k線別忘囉。 二哈可以利用30天移動平均線與當日開盤價進行買點與賣點的計算。 二哈可以用五天期的平均成交量低於十天前的五天期平均成交量的 75%這策略來進行交易。 就來這兩個吧~ ~ 實作 ~ 二哈可以利用30天移動平均線與當日開盤價進行買點與賣點的計算 首先咱們先來完成這個需求,在投資股票時,有個東西你在看k線時,幾乎所有的開盤軟體都會提供,它就是移動平均線,其中它又有十天線、月線、季線、年線、二年線等,簡單來說,十天線就是用前十天的平均來計算出來的一條件,非常的Easy。 然後我們來看看我們的月線也就是30天期線的產生,首先先看看最外面有個變數temp,它是一個陣列,用來存放30天的開盤價,為了用來計算平均數用的,但有點注意,如果只是在外面宣告個var temp = []這樣在mongodb的mapreduce函數是無法使用的。 那要著麼使用呢 ? 拉下面一點會看到有個參數物件,其中的scope就是讓我們可以使用全域變數 temp 。 temp看完後在來看看我們的主體mapreduce,但在執行mapreduce之前,我們會先進行query,將code為8111的尋找出來,這邊有個金句要注意一下。 盡可能的在進行資料分析時,先將不需要的資料篩選剔除掉,這是個黃金法則。 var temp =[]; var result = db.stocks.mapReduce( function(){ if(temp.length < 30){ temp.push(this.open); emit(this.date,0); }else{ temp.splice(0,1); temp.push(this.open) var sum =0, avg =0, tempCount = temp.
上一篇文章中,我們已經說明完基本的架構以及索引和分片的選擇,接下來我們就要實際的來使用資料來進行一些分析,能用搜尋時就用搜尋,不能用搜尋時就改用 aggreagate framework,然後如果再不能的話則用 mapreduce。 ~ 二哈的分析需求 ~ 這二貨根本沒有想需求,只是想說來分析一下,但分析啥也沒說,然後還要我們幫他想一下,然後還用這種表情看我,一臉用這種事情還用問我的表情。 然後我們只能乖乖的幫他想幾個。 二哈最基本應該會輸入股價代碼,然後輸出該股票的全部資料。 二哈想尋找出該股票某段區間的資料。 二哈想找出當日交易最熱絡的股票。 二哈想找出某日價格波動最高的股票。 那我們先開始吧。 ~ 索引與片鍵的建立 ~ 呃對了,雖然上一篇文章中,我們已經將索引與片鍵選出來了,分別為 索引 : { "date" : 1 , "code" : 1 } 片鍵 : { "code" : 1 } 但咱們突然想到一件事,你要建立的片鍵,必須要有索引,當我們的索引是複合索引,這樣我們還可以使用{ "code" : 1 }來建立嗎? 我們來試試。 db.stocks.ensureIndex({ "date" : 1 , "code" : 1 }) sh.enableSharding("test") sh.shardCollection("test.stocks",{"code":1}) 結果如下,看來是不行。 那要著麼辦呢?這時我們有三個辦法。 再增加一個code索引。 選擇 { "date" : 1 }與{ "code" : 1}當索引。 片鍵修改為{ "date" : 1 , "code" : 1 }。 要選那個呢,首先先來說說第一個,增加一個索引,缺點就在於需要更多的空間,而且這索引搜尋時幾乎不太用到,因為幾乎被原本的 { "date" : 1 , "code" : 1 }可取代。
前面幾篇文章我們說明完了分片的運用後,我們接下來,就來實際的模擬個情景,我們來學習要如何的一步一步完成,咱們選擇的模擬情境為股價應用,現在Fintech幾乎天天在報紙上看到,所以我們就來應景一下,來嘗試這建立看看金融應用。 ~ 情景說明 ~ 二哈是一位二貨,他平常就有在進行投資,大部份都是買買股票,但平常都只是直接卷商的平台看看資料,然後就直接投資囉,但是這貨兒每買必輸每賣必虧,然後有一天他聽到天賴之音說『請分析一下』,然後它就決定走上資料分析一途……這貨真的很二 回歸主題,二哈的需求只是分析,所以我們再分析前,我們要先建立好資料,通常能分析的資料量是越大越好,所以我們這邊一定會需要用到分片,並且我們先從最基本的股票資料k線與成交量來建立資料,首先我們的資料結構應該如下。 { 股價代碼 "code" : 1011, 日期 "date" : 20160101, 開盤價 "open" : 100, 最高價 "height" : 100, 收盤價 "close" : 90, 最低價 "low" : 80, 成交量 "volume" : 1000 } 然後我們來正試開始吧。 ~ Step 1. 架構分析 ~ 索引架構思考 首先我們根據以上的資料結構可知,我們該主題目前不太需要考慮到正規化與反正規化的問題,那接下來我們來思考看看索引的問題,但那蠢二哈只想到分析但不知道分析啥,我們來幫他想想。 我們來一條一條列出,我們想到的需求。 二哈最基本應該會輸入股價代碼,然後輸出該股票的全部資料。 二哈想尋找出該股票某段區間的資料。 二哈想找出當日交易最熱絡的股票。 二哈想找出某日價格波動最高的股票。 細細想一下,大部份的使用情境都一定需要時間,而且是個範圍,然後有時在搭配某個股票,所以我們基本上會針對date和code來考慮建立索引,那要選用那種索引呢,目前有三種選擇我們先列出。 第一種 { "date" : 1 , "code" : 1 } 第二種 { "code" : 1 , "date" : 1 } 第三種 { "code" :1 },{ "date" :1 } 還記得{ "sortKey" : 1 , "queryKey" : 1 }這個複合索引時有提到的東西麻,很常用來排序的請放前面,日期和股價代碼,理論上來說日期會很常用到排序,所以我們第二種索引可以刪除。
上一篇文章我們詳細的說明完分片的機制後,接下來我們就要來詳細的說明片鍵的選擇,片鍵的選擇關係到你的分片執行速度與效能,並且一但建立後,要再修改幾乎是不太可能的,所以請像選老婆一樣,用心的選~ 完美的片鍵定義(這鬼不存在的) 片鍵的種類 ~ 完美的片鍵定義(這鬼不存在) ~ 在我們開始學習片鍵的選擇前,我們要先知道,什麼樣的片鍵是最好的,最理想的,但想也知道最好的東西是不存在的,但我們還是要知道,才能給我們的選擇給個基準。 整體來說完美的片鍵有下面特性 所有的新增、刪除、更新等寫的操作,都可以平均分配到所有的分片。 所有的搜尋等讀的操作,都可以平均分配到所有的分片。 所有的操作都只會發到相關的分片,例如更新時不會跑去分片內是空的進行更新。 我們先來說說第一個特性,如果沒有該項特性會發生什麼事情,假設我們的cluster有四個分片,我們當然是希望每個分片可以處理25%的事情,但是假設我們做那些寫的操作時(ex.新增)全部都集中在其中一個分片,那你會發覺那個分片會越來越大~越來越大~ ,而且別忘了我們上章節說的chunk分配,它是根據數量來進行分配,不是用大小,因此你的那個分片內的chunk不會分配到其它分片,這樣也就失去你用分片的意義了。 而至於第二點特性,mongos在處理搜尋請求時主要會分成下述兩種的處理方式。 搜尋時不包含片鍵,則會將搜尋分配到發有的分片,然後合并搜尋結果,再返回給client。 搜尋時包含片鍵,則直接根據片鍵,然後找出要尋找的chunk,向相對的分片發送搜尋請求。 根據上面的說明,我們知道mongos的讀操作過程,然後我們這時在回來思考,如果這時搜尋時都集中在一個分片上,會發生什麼事,首先搜尋時不包含片鍵這種類型影響不大,但另一種就會影響到,因為原本該分散的壓力,反而都集中在一個分片,運氣不好搜尋請求過多,就爆掉了。 而至於第三點,就只是浪費資源囉~ 這邊我們大概來整理一下,根據以上三點大概可以拆分成幾個良好片鍵的特性。 容易分割片鍵 : 容易分割的片鍵可以使mongodb更容易的均衡各分片的資料量,不容易發生過大的分片,基數越大的走容易分割資料。 基數是指系統將資料分成chunk的能力,例如性別欄位就是個低基數的例子,只有男與女。 高隨機性的片鍵 : 具有越高隨機性的片鍵,他所分割出來的資料越容易均衡,也代表可避免任何一個分片承受過多的壓力。 可以指向單個分片的片鍵 : 越可指向單個分片的片鍵,越能降低搜尋效能壓力,像是隨機型的片鍵就無法做到。 這世界沒有著麼美好的事情,基本上幾乎找不到完全符合上述的條件,所以相對的咱們只能選擇盡可能符合你需要的片鍵,而這時就只能根據你專案的需求來決定,例如說這專案是讀吃比較重還是寫吃比較重,比較最大的搜尋條件,或搜尋時間過久的搜尋,這時都是要考量的。 ~ 一些片鍵的種類 ~ 這邊開始我們就要來說明一些片鍵的種類。 升序片鍵 這種類型的片鍵,大部份都是欄位為Date類型或是ObjectId,是種會根據時間來增加欄位,或是根據先後順序進來的欄位。 假如我們使用這種欄位做為片鍵,會發生什麼事情 ? 一開始建立片鍵時你不會看到什麼問題,而是再於你新增時會發生,假設我們有下面的分片cluster。 shard001 shard002 shard003 {min~2000} {2007~2008} {2014~2016} {2001~2003} {2009~2010} {2017~max} {2004~2006} {2011~2013} 然後這時我們要問個問題,我們進行新增時,它會加到那個chunk?
在上一篇文章中說明完基本的分片概念後,我們本章節要更深的了解分片內的chunk, 它是每個分片組成的東西,我們這篇將要說明它的拆分與分配機制。 chunk的分配與拆分。 ~ chunk 的分配與拆分 ~ 在上一篇文章中,我們知道每個分片中都包含了多個chunk,而每chunk中,又包含了某個範圍的document組,我們先簡單來畫個圖複習一下。 然後我們接下來要討論的就是,mongodb是如何拆分chunk和如何將chunk分片到shard裡,首先我們先來看看chunk的拆分。 chunk 的拆分 首先我們先想一下,chunk它本身是一堆document的集合體,大概長降,我們使用上一章節的範例,來看一下chunk的詳細資訊,假設我們都已經分片好了,我們直接看結果。 首先我們需要先移動到一個名為config的資料庫。 use config > switched to db config 然後再執行db.chunks.find().pretty()來看一下,目前只有一個chunk,它目前窩在shard0000,而它的範圍是min ~ max,呃對了忘了說,我們的資料是1萬筆的{"name":"user"+i}這種物件。 這時我們要問個問題囉,它什麼時後會再分成另一個chunk ? 答案是chunk的大小,mongodb預設chunk最大限制為64MB,當超過時mongos會將它拆分為兩塊chunk,如下圖,此圖為官方圖片。 預設是64MB,當然我們也有辦法修改預設,指令如下,下面32代表為32MB。 use config > switched to db config db.settings.save({"_id" : "chunksize" : "value" : 32}) 但是這邊要修改大小時有幾點要思考一下。 chunk 越小時可以使分片的可以使分片的資料量更均衡,不會有差距太大的狀況,但缺點就是,因為小所以會常移動chunk,所以mongos壓力會比較重。 chunk 的拆分實驗 咱們來簡單的測試看看chunk的拆分,首先來建立一些資料,大小約為4188890 byte大概為4mb左右,然後我們的chunk大小預設為1mb,所以理論上應會開拆為3~4個chunk。 var objs = []; for (var i=0;i<100000;i++){ objs.push({"name":"user"+i}); } db.users.insert(objs); 建好後別忘了執行這兩個指令。 db.users.ensureIndex({"name":1}) sh.shardCollection("test.users",{"name":1}) 然後我們指行sh.status()來看看結果,呃我淚囉為什麼會拆分為8個…… 我們來檢查一下chunk size的設定,如下圖嗯沒錯~是1。 use config db.
本篇文章將要說明 mongodb 的分片`,上一章節說明了如何將資料同步到其它台節點上,而本篇文章是將要說明,如何將資料分割到其它台節點,讓我們可以更快速、更多容量空間的來做一些哩哩扣扣的事情。 分片原理。 分片實作。 ~分片原理~ 分片是啥 ? 它主要的概念就是將collection拆分,將其分散到不同的機器,來分擔單一server的壓力。 咱們先來看看我們平常單一server的mongodb結構,其中mongod就代表我們實際上存放資料的地方,它平常都是指令和client端通信,client就有點像咱們平常用的mongodb shell之類的。 而咱們在來看看,如果用了分片會變啥樣,如下圖,三個mongod都會統一通信到mongos,在和client進行通訊,mongos不存儲任何資料,它就是個路由server,你要什麼資料就發給它,它在去決定去那個mongod裡尋找資料。 那這邊有個問題來囉~這三個mongod要著麼決定誰要存放那些資料 ? 答案是下面標題片鍵~ 片鍵 Shard Keys 片鍵是啥 ? 它就是當你要進行分片時,你選定的collection切分的依據,假設我們有下面的資料。 { "name":"mark" , "age" :18} { "name":"steven" , "age" :20} { "name":"ian" , "age" :20} { "name":"jack" , "age" :30} { "name":"stanly" , "age" :31} { "name":"jiro" , "age" :32} { "name":"hello" , "age" :41} { "name":"world" , "age" :52} ... ... { "name":"ho","age" : 100} 它就有可能會分片成這樣,假設咱們拆分為三片,然後我們指定片鍵為age欄位,它就大致上可能會分成這樣,會根據片鍵建立chunk,然後再將這堆chunk分散到這幾個分片中,{min~10}就是一個chunk,就是一組document。
上篇文章我們已經說明完,如何在本機上建立 mongodb 副本集,而本篇文章,我們將要實際的使用docker來建立有三個節點的副本集,也就是所謂 cluster 。 開始前的準備 建立架構圖 fight ! ~開始前的準備~ 首先再開始之前你當然要先將docker裝好,可以參考下面這章,但你的docker compose那邊可以不用做到,因為我還沒研究出,如何用docker compose來建立cluster……QQ。 30-2之使用Docker來建構MongoDB環境 確定執行docker --version有類似下面的資訊出來就ok囉。 Docker version 1.12.3, build 6b644ec 接下來呢咱們需要下載mongodb image,平常我們都是用docker compose直接執行它都會幫我們偷偷下載好,而現在我們就需要自已下載,指令如下。 docker pull mongo 然後咱們就都準備好囉。 ~建立架構圖~ 我們來看看下圖,首先我們會先建立一個cluster取名為my-mongo-cluster,然後裡面有三個mongodb並且對外連接port設為30001、30002、30003,並且這三個的container都可以互相溝通。 ~建立流程~ Fight ! step1 將my-mongo-cluster加入到docker network裡 我們先執行看看docker network ls然後會出現下圖的列表。 然後我們再執行下面的指令將新增個network到docker network裡。 docker network create my-mongo-cluster 然後你就可以看到我們將my-mongo-cluster加入至docker network裡。 Step2 建立三個 MongoDB 的 Container,並加入至 my-mongo-cluster 這 network 中 首先來看看指令,然後我們來解釋一下每個指令是啥意思。 docker run : 就只是執行docker而以。 -p 30001:27017 : 將port:27017暴露出來,為了讓其它mongodb可連接到,而30001則為該container的本機port。 --name mongo1 : 將該container命名為mongo1。 --net my-mongo-cluster : 將該container加入到my-mongo-cluster這docker network裡面,然它們可以互相通信。 mongo mongod --replSet my-mongo-set : 運行mongod時將該mongo加入到名為my-mongo-set的副本集中。 docker run -p 30001:27017 --name mongo1 --net my-mongo-cluster mongo mongod --replSet my-mongo-set 記好上面這些是要縮成一行來執行,如下。
本篇文章將要說明, mongodb 的副本集。嗯~想想一個情況,現在咱們只使用一台 server 來存放資料,我們現在只是測試和開發, GG 囉也只是啊一聲,但如果是正式上線環境呢 ? GG 囉可不是啊一聲就可以解決的,你可能就被老闆不要不要的,很慘的~ 而副本集就是用來解決這問題,事實上也就只是被備份。 副本集原理 副本集建立(單機版給你好測試) ~副本集原理~ 首先我們先看看mongodb官網所提供的圖。 上面這張圖,你可以想成這個系統它有三個mongodb,其中primary節點接受所有client端的讀或寫,整個副本集只有一個primary,並且每當有資料新增時,primary會同步到其它兩個secondary。 然後當primary節點GG的話,會變成下面這張圖的結果(一樣來至官網)。 在這裡面,各節點都是通過一個叫心跳請求(heartbeat request)的機制來通信,如果當primary節點如果在10秒內無法和其它節點進行通信,這系統會自動從secondary節點中選取一個當主節點。 ~副本集建立~ 在上面大概簡單的了解完它的原理後,我們就實際上的來操作看看,首先我們執行下面指令, 來進行到沒有db的mongodb shell環境。 mongo --nodb 然後通過下面的指令,就可以建立一個副本集,其中nodes : 3代表三個節點,一個primary其它兩個為secondary。 replicaSet = new ReplSetTest({"nodes":3}) 不過執行完上面這行指令它還沒啟動喔還需要執行下面兩行,startSet為啟動那三個節點的進程,而initiate為設定複制功能。 replicaSet.startSet() replicaSet.initiate() 當執行完上面兩行後,我們就要跳到另一個Shell,然後連接到primary的節點,喲~?那它的port是啥?雖然有些文章中說預設是31000、31001、31002但我的電腦卻不是,所以建議還是在執行startSet時看一下,它應該會輸出下面這張圖的資訊。 嗯看到了吧,通常第一個就是primary,不是的話就試試其它的,然後我們這時就可以執行下面指令進入到它的裡面了。 conn1 = new Mongo("127.0.0.1:20000") 接下來我們就可以執行一些指令來看看這個副本集的狀態。 primaryDB = conn1.getDB("test") primaryDB.isMaster() 結果如下,其中isMaster這欄位就是說明這節點是primary節點。 ~驗證一下有沒有備份到 secondary 節點~ 首先我們先新增一些資料。 var objs = []; for (var i=0;i<10;i++){ objs.push({"name":"user"+i}); } primaryDB.users.insert(objs); 然後我們這時連到secondary。 conn2 = new Mongo("127.0.0.1:20001") 進去後在輸入。 secondaryDB = conn2.
上篇文章中,基本上已經把po文的方法,大部份都完成了,也建立好了索引,並且也將po文常見的搜尋給實作出來,接下來本篇文章,我們將要站在資料分析者的角度,使用聚合工作Aggregate framework與MapReduce來進行一些分析案例,一樣為了怕使用者忘記需求,我們還是再再貼一次~~ ~需求說明~ 我們這邊想要簡單的模擬FB的貼文,我們可以新增貼文或做一些事情,並且我們希望還可以進行一些貼文分析,最後這項模擬會建立在有100萬筆下貼文的情況下,所以我們簡單的先列出我們可以用的功能。 使用者可以簡單的新增發文,並且會存放Text、Date、Author、likes、Message。(完成) 建立100萬筆模擬po文。(完成) 使用者可以刪除發文。(完成) 使用者可以對自已的po文進行更新。(完成) 使用者可以進行留言和刪除留言。(完成) 使用者可以like發文。(完成) 使用者可以根據Text、Author、likes、Date進行搜尋。(完成) 管理者可以速行分析個案(已經想到囉如下) ~分析個案 (需求8)~ Boss希望可以知道最多人留言的貼文,並且該貼文中前三位留言最熱絡的使用者,並計算留言次數。 Boss想知道最近貼文中最長出現的『單詞』是啥 ? 可以讓老大知道最近最熱門的東西 ~ 嗯……才兩個好像有點少,但你往下來就知道可以寫很多了,都是要動腦想三下著麼做的啊…… 1. Boss希望可以知道最多人留言的貼文,並且知道該貼文中,前三位留言最熱絡的使用者,並計算留言次數。 首先先來解決這需求,仔細看看不太難,將需求拆解成如下步驟就好。 將每筆貼文的留言數量計算出來,並存放在messagesCount這變數中。 根據messagesCount進行排序。 將排序好的資料取第一個。 在將messages中的author來進行分組統計,並將結果存放在count中。 在針對count進行排序,取前三名。 交給Boss看…… 根據以上的步驟我們使用mongodb的aggreagate framework來寫出下列程式碼,啊咧啊咧…… 著麼只寫到步驟2的排序…… ? db.posts.aggregate( { "$project" : { "messagesCount" : { "$size" : "$messages" } } }, { "$sort" : { "messagesCount" : 1} } ) 因為GG了,咱們的排序所耗用的記憶體超過mongodb的限制囉,請看下圖~ 網路上有人推薦說,在建立document時就多建立一個欄位,來存放它的數量,然後直接建立索引,但在我們這邊是會GG掉的,因為我們的留言隨時都在變,而且沒新增或刪除個留言都還要去對那個存放欄位進行更新,而且還有索引,這樣會讓咱們的效能大大的下降,所以在這應用中否定這選項~ 那要著麼辦呢 ? 後來又查到一個方法,那就是allowDiskUse 參數,mongodb有個限制在Pipeline的階段中,規定記憶體只能用100mb,不然就會跳出上圖的錯誤,但如果將allowDiskUse設定為true,則它多出來的資料暫存寫入到臨時的collection,只是會不會有什麼問題或壞處,官網上都沒特別提到…… 繼續正題,然後解決完這個sort的問題後,我們就可以使取得貼文的留言數最多的貼文。 db.posts.aggregate( [ { "$project" : { "messagesCount" : { "$size" : "$messages" },"messages" : 1 } }, { "$sort" : { "messagesCount" : -1}}, { "$limit" : 1} ], { allowDiskUse: true } ) 咱們再繼續往下寫,取得了最多留言數的貼文後,我們要繼續來尋找留言最多的人是那位,我們先使用$unwind將messages的陣列欄位,拆分成多個document,以方便我們用來group,再下來我們就可以根據messages的author來進行分組,並且計算每一組的數量存放至count來欄位。
上篇文章中,咱們已經將資料都建立好了,也完成了第一個需求,使用者可以進行PO文,並且我們建立出了模擬資料共一百萬筆,大約1gb的大小,接下來我們這篇文章將繼續完成需求,為了怕讀者們忘了需求,所以還是在貼一次。  ~需求說明~ 我們這邊想要簡單的模擬FB的貼文,我們可以新增貼文或做一些事情,並且我們希望還可以進行一些貼文分析,最後這項模擬會建立在有100萬筆下貼文的情況下,所以我們簡單的先列出我們可以用的功能。 使用者可以簡單的新增發文,並且會存放Text、Date、Author、likes、Message。 (完成) 建立100萬筆模擬po文。(完成) 使用者可以刪除發文。 使用者可以對自已的po文進行更新。 使用者可以進行留言和刪除留言。 使用者可以like發文。 使用者可以根據Text、Author、likes、Date進行搜尋。 管理者可以進行分析個案(那些個案之後再想) 以下的步驟不代表上述列表的序號,而只是我們完成這需求的過程。 Step5 (需求3) 使用者可以刪除發文 這個刪除的方法事實上不太難,使用者只要輸入該po文的objectId就可以進行刪除,當然如果是實際有畫面的當然是直接給你選你要刪除的發文,不會還叫你輸入objectId,程式碼如下。 db.posts.remove ({"_id" : "xxxxxxx"}) 不過在使用刪除時有些事情也要想一下,如果是指定objectId來刪除,理論上來說只會刪除一個,並且速度很快,因為objectId系統會自動的幫我們建立索引,但如果是其它的query則可能要根據情況來考慮要不要建立索引,來幫助刪除的更快速,並且如果要刪除多筆資料別忘了使用bulk。 var bulk = db.posts.initializeUnorderedBulkOp(); bulk.find( { "name": "mark" } ).remove(); bulk.execute(); 刪除方面可以看看這篇文章來複習複習 ~ Step6 (需求4) 使用者可以對自已的po文進行更新 這個也很easy~,就只是針對它的objectId進行搜尋然後更新就好,在做的過程中我們需要使用修改器$set,它的功能就是只針對指定的欄位進行修改~別忘囉~ , db.posts.update({"_id":"XXXXX"}, {"$set" : { "text" : "Hello World" } ) 更新複習請看這篇~ Step7 (需求5) 使用者可以針對po文進行留言和刪除 先來回想一下我們的posts結構長啥樣子,如下~ { "id" : 1, "text" : "XXXXXX", "date" : "20160101", "author" : "mark" , "likes" : 1, "messages" : [ {"author" : "steven" , "msg" : "what fuc.
咱們來細數一下,我們在前面的幾篇學了那些東西~ mongodb 的新增、刪除、更新、搜尋。 mongodb 的索引運用。 mongodb 的資料分析工具 Aggregate 聚合。 mongodb 的設計。 是的~雖然看起來很少,但基本上基礎都差不多學會了,接下幾篇我們將要實際上的寫寫程式,來將我們之前學習到的東西都複習一次。 ~需求說明~ 我們這邊想要簡單的模擬FB的貼文,我們可以新增貼文或做一些事情,並且我們希望還可以進行一些貼文分析,最後這項模擬會建立在有100萬筆下貼文的情況下,我們簡單的先列出我們要做的需求。 使用者可以簡單的新增發文,並且會存放Text、Date、Author、likes、Message。 建立100萬筆模擬po文。 使用者可以刪除發文。 使用者可以對自已的po文進行更新。 使用者可以進行留言。 使用者可以like發文。 使用者可以根據Text、Author、likes、Date進行搜尋。 管理者可以速行分析個案(那些個案之後再想) 以下的步驟不代表上述列表的序號,而只是我們完成這需求的過程。 Step1 . 先想想 MongoDB 的架構 首先咱們先來想想,我們應該會有一個collection是會存放貼文資料,我們就取名為posts,然後再想想他裡面大概會長成啥樣,應該是如下的json。 { "id" : 1, "text" : "XXXXXX", "date" : "20160101", "author" : ?? , "likes" : 1, "message" : ?? } 這時應該遇到兩個問題,author與message的格式如何,author應該是比較簡單,應該只要建立者的name,但這時你要考慮一件事,要不要為使用者建立個users的collection,首先回答幾個以下幾個問題。 使用者資料在其它地方會不會使用到 ? Ans:會的,在留言時會需要用到。 使用者是否會高的頻率修改name ? Ans:不會,頻率很低。 根據上述回答,第一點是建議正規化,而第二點則是建議反正規化,那麼要選擇那個呢? 因為我們這case比較注重搜尋的速度,所以建議選用『反正規化』,也就是如下的結構,而不另外建立users的collection。 { "id" : 1, "text" : "XXXXXX", "date" : "20160101", "author" : "mark" , "likes" : 1, "messages" : ?
本篇文章將說要說如何設計mongodb的架構,讓你可以更快速的使用mongodb。 資料庫的正規化(文鄒鄒)。 mongodb正規化與反正規化。 該選用那個方法呢 ? ~ 正規化 ~ 在開始討論mongodb架構時,有個東西要先講講,那就是『正規化』與『反正規化』,有使用過資料庫的應該都有聽過這名詞,不過這邊還是來解釋解釋,順到回憶一下。 首先什麼是正規化呢 ? 根據wiki的定義。 Database normalization is the process of organizing the fields and tables of a relational database to minimize redundancy and dependency. 中文意思為。 資料庫正規化就是指將關聯式資料庫的欄位與表單進行讓『資料重複性與相依性』能夠降到最低的組織過程。 是的,真的很文鄒鄒,不過我們只要知道正規化的目的是解決資料的『重複性』與『相依性』這兩個點就夠囉,資料庫正規化有一些規則,每條規則都稱為『正規形式』,符合第一條規則就稱為『第一正規形式』,總共有不少條,但通常到『第三正規形式』就被視為最高級的正規形式, 下面來簡單的說明一下這幾條規則。 第一正規形式 以下條列為第一正規形式的規則,事實上重點還是在說『不要有重複群組』。 刪除各個資料表中的重複群組。 為每一組關聯的資料建立不同的資料表。 使用主索引鍵識別每一組關聯的資料。 我們假設資料為每個人的交易資料,下表為違反正規化的資料結構,因為它有重複的群組Volume,並且也缺少主索引鍵來識別每一組關聯的資料。 Name Date Volume Mark 20160101 10 , -20 Jiro 20160102 -20 , 30 Ian 20160103 34 , -10 如果要符合第一正規形式大概要長的像降。
前兩篇說明完 mongodb 所提供的第一種聚合工作 aggregate framework ,本篇文章將要說明 mongodb 所提供的第二種聚合工作, MapReduce` 嗯…只要有微微研究過大數據,應該都有聽個這個潮潮的名詞,尤其應該有不少人有看過這篇『我是如何向老婆解释MapReduce的?』,不過它原文版好像消失了,扣惜。 ~MapReduce~ MapReduce是google所提出的軟體架構,主要用來處理大量的數據,而mongodb根據它的架構建構出可以在mongodb中使用的聚合工作,MapReduce它可以將一個複雜的問題拆分為多個小問題(map),然後發送到不同的機器上,完成時再合併為一個解決方案(reduce),簡單的畫張圖來看看。 但這個方法和aggregate framework有什麼差別 ? aggregate framework 提供較優透的性能。 MapReduce性能較差,但可提供更複雜的聚合功能。 ~ Mongodb 的 MapReduce 使用~ mongodb中的MapReduce使用的方法如下。 db.collection.mapReduce( map, reduce, { <out>, <query>, <sort>, <limit>, <finalize>, <scope> } ) 其中參數的說明如下。 參數 說明 map map函數,主要功能為產生key給reduce。 reduce reduce函數。 out 輸出結果集合的名稱。 query 在map前,可用query先進行篩選。 sort 在map前,可用sort進行排序。 limit 在map前,可限制數量。 finalize 可以將reduce的結果,丟給某個key。 scope 可以在js中使用變數。 實際應用1 ~ 根據 class 分組計算每組訂單收入 是的,這個例子我們在aggregate framework時有用過,事實上這種簡單的例子用MapReduce來解決,有點用到牛刀了,不過我們只是要看看如何使用,所以就不用在意太多囉。
在上一篇文章中說明了 pipeline 操作符號,接下來我們這篇要說明在操作符號內使用的 pipeline 表達式,它讓我們可以在pipeline 內進行計算、比較、字串修改等分析方法。 數學表達式(mathematical expression) 日期表達式(date expression) 字串表達式(string expression) 邏輯表達式(logical expression) ~ 數學表達式 ~ 以下列表為比較常用的數學表達式(全部在這)。 表達式 說明 $add 接受多個表達式,然後相加。 $subtract 接受兩個表達式,用第一個減去第二個作為結果。 $multiply| 接受多個表達式,然後相乘。 $divide 接受兩個表達式,然後相除。 $mod 接受個表達式,然後相除取餘。 實際運用 ~ 我們想要知道訂單總收入是多少。 我們來看看實際上是如何運用,假設我們有下列資料,該資料為訂單資料。 { "id" : 1 , "price" : 100 , "count" : 20, "discount" : 0 }, { "id" : 2 , "price" : 200 , "count" : 20, "discount" : 100 }, { "id" : 3 , "price" : 50 , "count" : 20, "discount" : 100 }, { "id" : 4 , "price" : 10 , "count" : 210, "discount" : 200 }, { "id" : 5 , "price" : 100 , "count" : 30, "discount" : 20 } 這個應用中,我們希望知道總收入是多少,以下為收入公式。
在前面幾篇都是說明如何尋找到你想要的東西,而在接下來的聚合章節中,我們將說來學習到如何使用聚合工具,來幫助我們分析更多資料,以下為本篇要說明的事情。 聚合 (aggregate) 是啥 ? 有啥用。 Mongodb 聚合工具 Aggregate Framework。 管道 pipeline 操作符號。 ~ 聚合(aggregate)是啥?有啥用? ~ 在前面幾篇文章中,我們學會了mongodb的CRUD,以及使用索引讓我們搜尋、排序速度更快速,那我們接下來幾篇要學什麼?答案就是『分析』,是的,我們將資料存放進mongodb最終的目的就是要使用分析,而聚合就是能幫助我們分析的工具,它能處理數據記錄並回傳結果。 ~ MongoDb 聚合工具之 Aggregate Framework ~ 在mongodb中提供了aggregate framework的聚合工具,使用方法如下,其中AGGREGATE_OPERATION就是指你每一次的處理過程。 db.collection.aggregate(AGGREGATE_OPERATION) 先不考慮mongodb的語言,下面就是一個聚合的範例,mongodb的aggregate framework主要是建立在聚合管道(pipeline)基礎下,而這管道就是可以一連串的處理事件,以下列範例中你可以想成管道中有四節,『將每篇文章作者與like數抓取出來』為第一節,然後它處理完會產生資料,會再丟給第二節[依作者進行分類],直到最後產生結果。 db.collection.aggregate( [將每篇文章作者與like數抓取出來], [依作者進行分類], [將like數進行加總] [返like數前五多的結果] ) ~ 管道 pipeline 操作符號 ~ Aggregate framework提供了很多的操作符號,來幫助你進行複雜的操作,每個符號都會接受一堆document,並對這些document做些操作,然後再將結果傳至下一個pipeline直到最後結果出現。 project 使用$project可以用來選取document中的欄位,還可以在這些欄位上進行一些操作,或是新建欄位。 下面寫個簡單的使用範例。 首先我們有下列的資料。 { "id" : 1, "name" : "mark", "age" : 20, "assets" : 100000000 } 然後我們可以用$project來決定要那個欄位,我們選取id與name欄位。 db.user.aggregate({ "$project" : { "id" : 1, "name" : 1 }}) 結果如下,當然他的功能沒著麼單純,它還可以和很多東西搭配,晚點會說。
本篇文章將要說明幾個比較特別索引使用的方法。 索引陣列欄位 索引子欄位 全文索引 P.S 快要一半囉~~+u^13 ~ 索引陣列欄位 ~ 假設你有下列資料,但發現搜尋fans裡的值很慢,你想要建立索引,要著麼建呢? { "name" : "mark" , "fans" : ["steven","jack","mmark"]} { "name" : "steven" , "fans" : ["max","jack","mmark"]} { "name" : "jack" , "fans" : ["steven","hello","mmark"]} 事實上就和之前幾篇建立索引一樣。 db.user.ensureIndex({"fans":1}) 那我們在再假設資料如下。 { "name" : "mark" , "fans" : [ {"name" : "a" , "age" :11}, {"name" : "b" , "age" :10}, {"name" : "c" , "age" :21}, ] }, { "name" : "steven" , "fans" : [ {"name" : "e" , "age" :10}, {"name" : "f" , "age" :20}, {"name" : "c" , "age" :21}, ] } 這時如果我們建立fans裡的name為索引,指令會如下。
本文將會說明以下幾點。 複合索引是啥~ 複合索引的運用與坑坑坑~ ~ 複合索引是啥 ~ 假設有下列資料。 { "name" : "mark" , "age" : 20} { "name" : "mark" , "age" : 25} { "name" : "steven" , "age" : 30} { "name" : "max" , "age" : 15} 在上一篇文章中說到,如果要建立name的索引,是像下面這樣。 db.user.ensureIndex({"name" : 1}) 這時mongodb就會大致上~將索引建成如下。 索引目錄 存放位置 ["mark"] -> xxxxxxxx ["mark"] -> xxxxxxxx ["max"] -> xxxxxxxx ["steven"] -> xxxxxxxx 而所謂的複合索引事實上就是只是針對多個欄位建立索引,如下。 db.user.ensureIndex({"name" : 1 , "age" : 1}) 而mongodb就會建立索引如下。 索引目錄 存放位置 ["mark",20] -> xxxxxxxx ["mark",25] -> xxxxxxxx ["max",15] -> xxxxxxxx ["steven",30] -> xxxxxxxx ~ 複合索引的運用與坑坑坑 ~ 在前一篇文章中有說過,索引是把雙刃刀,建立的不好反而會浪費更多資源,而複合索引更是雙刃刀中連握把可能都有刀刃,以下舉個例子來說明~說明~
本篇文章將會說明以下幾點。 什麼是索引? 索引的優點與缺點 索引的建立 索引與非索引搜尋比較 不要使用索引的時機 P.S +u^11鐵人們 ~ 事實上我已快gg了 ~ 什麼是索引? ~ 索引是什麼?最常見的說法是,一本字典中,你要找單字,會先去前面的索引找他在第幾頁,是的這就是索引,可以幫助我們更快速的尋找到document,下面畫張圖來比較一下不使用索引和使用索引的搜尋概念圖。 ~ 索引的優缺點 ~ 索引竟然可以幫助我們著麼快的找到目標,那是不是以後都用索引就好??著麼可能!~ 索引好歸好,但他就像雙刃刀,用的不好會gg的。 優點 搜尋速度更(飛)快 ~ 在使用分組或排度時更快 ~ 缺點 每次進行操作(新增、更新、刪除)時,都會更費時,因為也要修改索引。 索引需要佔據空間。 使用時機 所以根據以上的優缺點可知,不是什麼都要建立索引的,通常只有下列時機才會使用。 搜尋結果佔原collection越小,才越適合(下面會說明更清楚)。 常用的搜尋。 該搜尋造成性能瓶頸。 在經常需要排序的搜尋。 當索引性能大於操作性能時。 ~ 索引的建立 ~ 我們簡單建立個索引使用範例。 db.tests.insert( {"x" : "hello"} ) 然後這時我們建立x欄位的索引。 db.tests.ensureIndex({ "x" : 1 }) 然後我們可以達行下列指令,來查看有沒有建立成功。 db.tests.getIndexs() 結果如下,建立成功x的索引,其中_id那個是預設的,mongodb會自動幫objectId建立索引。 ~ 索引與非索引搜尋比較 ~ 在mongodb中排序是非常的耗費內存資源,如果排序時內存耗費到32mb(這裡),mongodb就會報錯,如果超出值,那麼必須使用索引來獲取經過排序的結果。 我們這裡建立些資料,來比較看看兩者的資源耗費不同點。 for (var i=0;i<100000;i++){ db.test.insert({ "x" : i }) } 然後建立x的索引。
本篇文章將要說明cursor的用法以及一些curosr的方法,可以搜尋後用來限制或排序結果的功能,以及說明一下在不考慮索引情況下find的搜尋原理。 Cursor是啥 Cursor的方法 搜尋的原理 P.S 三分之一囉,也代表基本的mongodb的crud要Ending囉。 ~ Cursor 是啥 ~ cursor是find時回傳的結果,它可以讓使用者對最終結果進行有效的控制,它事實上也就是Iterator 模式的實作。 除了可以控制最終結果以外,它另一個好處是可以一次查看一條結果,像之前insertMany時,他會一次回傳全部的結果,mongodb shell就會自動一直輸出,結果看不到後來執行的東西。 我們實際來看一下cursor的用法,首先我們還是要先新增一些資料。 for (var i=0;i<10;i++){ db.test.insert({x:i}) } 然後進行搜尋,並用一個變數cursor存放。 var cursor = db.test.find(); while (cursor.hasNext()){ obj = cursor.next(); print(obj.x + " ~呼呼~") } 結行結果如下圖。 ~ Cursor 的方法 ~ limit、skip、sort這三個是很常用的cursor方法,主要功能就是限制、忽略、排序。 limit 要限制find結果的數量可以用limit,不過注意limit是指定上限而不是指定下限, 使用方法如下,limit(10)就是代表最多只回傳10筆資料。 db.test.find().limit(10) skip 當你想要忽略前面幾筆,在開始回傳值時,就是可以用skip,使用方法如下,skip(10),代表忽略前十筆,然後在開始回傳,不過注意『 skip如果數量很多時速度會變很慢 』。 db.test.find().skip(10) sort sort它主要就是將find出的資料,根據條件,進行排序。 例如假設我們有以下的資料。 {"name":"mark" , age:20} {"name":"steven" , age:25} {"name":"max" , age:10} {"name":"stanly" , age:40} {"name":"crisis" , age:5} 然後我們希望可以根據age排序,由小到大,{age:1}代表由小到大,而{age:-1}則相反由大到小。
本篇文章將要說明其它幾個搜尋方法,包含如何搜尋document中的陣列欄位的值以及運用正規表達式regex 來進行搜尋。 搜尋陣列內容 正規表達式搜尋 ~ 搜尋陣列內容 ~ 這邊我們將要介紹幾個陣列搜尋符號$all、$size、$slice。 Tables Are $all 當需要尋找多個元素節合的document時,就可以使用它 $size 當要尋找特定長度的陣列時,就可以用它~ $slice 可以指定回傳的陣列指定的範例 ex. 10就為前十條,-10就為後十條。 $elemMatch 它會只針對陣列,進行多組query。 假設情況我們collection中有下列document。 {"id":"1","name":"mark", "fans":["steven","stanly","max"], "x":[10,20,30]}; {"id":"2","name":"steven", "fans":["max","stanly"], "x":[5,6,30]}; {"id":"3","name":"stanly", "fans":["steven","max"], "x":[15,6,30,40]}; {"id":"4","name":"max", "fans":["steven","stanly"], "x":[15,26,330,41,1]}; 我們這時想要尋找 fans 中同時有 steven、max 的網紅 我們這時就可以使用$all。 db.user.find({"fans":{"$all":["steven","max"]}}) 結果如下,應該是只找到mark、stanly這兩個人。 我們想要尋找 fans 總共有三位的網紅。 我們這時可以用$size,不過有點可惜的一件事,$size無法與搜尋條件(ex.$gte)使用,所以無法尋找3人以上之類的,通常要來實現這種需求就只能多加個欄位了。 我們來看看$size的使用方法。 db.user.find({"fans":{"$size" :3}}) 我們希望尋找 mark 的第一個 fans。 $slice主要功能就是將陣列切割只回傳你指定的範例。
前面幾篇已經說明完了新增、修改、刪除,最後咱們新手村之旅的尾巴將要說明搜尋,這個功能應該是我們最常會使用到的,請好好的學習。 find方法基本說明 find的搜尋條件(含搜尋故事) P.S +u^8~ find 方法基本說明 mongodb使用find來進行搜尋,它的第一個參數決定要那些資料,而第二個參數則決定要返回那些key。 基本的使用範例如下,首先我們先建立一些資料。 db.user.insert({"name":"mark","id":"1","age":20}); db.user.insert({"name":"steven","id":"2","age":20}); db.user.insert({"name":"jj","id":"3","age":25}); db.user.insert({"name":"bb","id":"4","age":20}); 我們想尋找到name為mark的document,並且我們希望回傳值只回傳id這個key就好,搜尋指令如下。 db.user.find({"name":"mark"},{"id" :1 }) 搜尋結果如下,它只回傳了key id的內容,但是可以看到_id也被回傳回來,因為在默認情況下_id這個key會自動被傳回來,如果真的不想它也回傳回來可以下達下列搜尋指令。 db.user.find({"name":"mark"},{"id" : 1,"_id":0}) ~ find 的搜尋條件 ~ 這邊我們將要說明find常用搜尋條件,and、or、大於等於、大於、小於、小於等於、包含、不包含,有了這些條件我們就可以更方便的尋找你所需要的document。 這邊簡單的整理成一張表來對應操作符號。 條件 操作符號 AND $and,另一種方法也可以直接在query中下{"key1","value1","key2":"value2"}| OR $or NOT $not NOR $nor 大於 $gt 大於等於 $gte 小於 $lt 小於等於 $lte 包含 $in 不包含 $nin 我們接下來會先產生幾筆測試資料,再來測試幾個搜尋故事。
本篇文章將要來說明MongoDB的刪除方法,rmoeve、deleteOne、deleteMany、bulk,並且簡單的比較一下速有有何差別。 MongoDB的刪除方法 比較一下速度 ~ MongoDB的刪除方法 ~ remove remove方法是mongodb裡最基本的刪除document的方法,但這邊要注意就算你刪除了 document它的index與預分配空間都不會刪除。 使用方法與參數如下 justOne預設false,代表query到幾個就會刪除幾個,true則只會刪第一個。 witeConecern為拋出異常的級別。 collation是3.4版開始支持的功能,可依照語言定義來針對文字的內容進行解讀,再還沒支持collation前一徑依字節來對比。 db.collection.remove( <query>, { justOne: <boolean>, writeConcern: <document>, collation: <document> } ) 使用範例如下,我們來新增三筆資料,然後刪除掉steven該筆資料。 db.user.insert({"name":"mark","age":23}); db.user.insert({"name":"steven","age":23}); db.user.insert({"name":"jj","age":23}); db.user.remove({"name":"steven"}) 刪除所有資料 remove可以用來刪除collection的所有資料,但還有另一種方法也是刪除collection的所有資料,那就是drop,但它同時會將index給全部刪除。 兩種的使用方法如下。 db.user.remove({}) db.user.drop() deleteMany與deleteOne deleteMany與deleteOne也是刪除的方法一種,就一個是刪除多筆和一個是單筆,和remove不同點大概只差在回傳值上,至於速度上等等來trytry看。 使用兩種方法的參數如下,與remove也大至差不多。 db.collection.deleteMany( <filter>, { writeConcern: <document>, collation: <document> } ) 使用範例如下。 db.user.insert({"name":"mark","age":23}); db.user.insert({"name":"steven","age":23}); db.user.insert({"name":"jj","age":23}); db.user.deleteMany({"name":"steven"}) db.user.deleteOne({"name":"jj"}) bulk delete bulk操作故明思意就是要來衝一下大筆資料刪除的效能方法。 使用方法如下。 //先新增二筆資料 var bulk = db.collection.initializeUnorderedBulkOp(); bulk.insert( { name: "mark"} ); bulk.
本篇文章將要說明陣列修改器 push,主要就是針對 document 中的陣列進行修改,同時他也可以搭配 each、slice、ne、addToSet、pop、pull 來使用。 陣列更新修改器攻略 呼好多…… ~ 陣列更新修改器攻略 ~ $push $push是陣列修改器,假如一個document中已經有陣列的結構,使用push會在陣列的尾末加入一個新元素,要是本來就沒有這個陣列,則會自動新建一筆。 使用方法如下範例,首先先新增一筆資料,然後新增加一個叫jack的fans。 db.user.insert({ "name" : "mark", "fans" : ["steven","crisis","stanly"] }) db.user.update({"name":"mark"}, {$push:{"fans" : "jack"} }) 結果如下圖。 $each $push一次新增只能新增一筆元素,而搭配$each就可以新增多筆。 使用方法如下範例,一樣首先新增一筆資料,然後這時我們一次新增三個fans分別為jack、landry、max。 db.user.insert({ "name" : "mark", "fans" : ["steven","crisis","stanly"] }) db.user.update({"name":"mark"}, {"$push" : {"fans" : {"$each" : ["jack","lnadry","max"]}}} ) 結果如下圖 $slice 如果你希望限制一個陣列的大小,就算多push進元素,也不要超過限制大小,這時你就可以用$slice,不過注意它是保留最後n個元素。 使用方法如下範例,新增一筆資料,然後我們希望fans人數不超過5人,但我們硬多塞一個人進去。 db.user.insert({ "name" : "mark", "fans" : ["steven","crisis","stanly"] }) db.user.update({"name":"mark"}, {"$push" : {"fans" : {"$each" : ["jack","lnadry","max"], "$slice" : -5 }}} ) 執行結果如下,可以看到第一位steven被刪除,只保留了最後5位。
本篇將要來說明MongoDB中更新文檔的方法,並且也同時會說明更新修改器的功能,它能幫助我們進行更有效率的更新。 基本更新方法Update。 更新修改器 ($set、$inc)。 更新修改器效能比較。 ~ 基本更新方法Update ~ Update函數主要的功用就如同字面所說,更新~,而使用方法如下,query就是指你要先尋找更新的目標條件,update就是你要更新的值。而另外三個參考請考下列。 upsert : 這個參數如果是true,代表如果沒有找到該更新的對像,則新增,反之則否,默認是false。 multi : 如果是false,則代表你query出多筆,他就只會更新第一筆,反之則都更新,默認是false( !注意multi只能在有修改器時才能用 )。 writeConcern : 拋出異常的級別。 db.collection.update( <query>, <update>, { upsert: <boolean>, multi: <boolean>, writeConcern: <document> } ) 下面來簡單示範一下用法。首先我們先新增三筆資料。 db.user.insert({"name":"mark","age":23}); db.user.insert({"name":"steven","age":23}); db.user.insert({"name":"jj","age":23}); 然後我們將名字為mark這人的age改為18,指令如下,query為{"name":"mark"},query的詳細用法會在find那邊詳詳細細的說明。 db.user.update({"name":"mark"},{"name":"mark","age":18}) 執行結果如下,不過誒……我只要更新age也,為啥要全部換掉? ~ 更新修改器 ( set、inc ) ~ 修改器 $set $set修改器主要的功用就是用來指定一個字段的值,不用像上面一樣整個替換掉。 所以如我們如果要將mark這位仁兄的age改為18只要下達下面的指令。 db.user.insert({"name":"mark","age":23}); db.user.insert({"name":"steven","age":23}); db.user.insert({"name":"jj","age":23}); db.user.update({"name":"mark"},{"$set" : { "age" : 18} }) 執行結果如下,成功更新為age為18 修改器 $inc 假設一下情景,假如有個投票網站、或是要存放訪客數的功能,每次更新時都是要+1,這種時後就可以用$inc來更新你的document,理論上來說速度應該會優於$set,等會兒會來測試一下。 注意$inc只能用在數值類型,否則就會提示Modifier $inc allowed for numbers only。
本篇文章會運用上一篇提到的二種新增方法insert、insertMany,以及另一種新增方法Bulk來做執行速度比較 ; 由於insertMany在mongodb shell執行完會直接輸出結果,所以如果有1萬筆資料他就會一直跑一直跑……跑到天荒地老,看不到我用來計算執行時間的方法,所以本測試打算用node js來建立測試方法。 在開始測試之前,先介紹一下另一個新增方法Bulk Insert。 Bulk Insert 方法 新增方法的效能測試 ~ Bulk Insert方法 ~ Bulk Insert在2.6版時發佈,它也是種新增方法,效能如何等等會比較,基本使用方法有分有兩Unordered Operations和Ordered Operations。 Ordered Operations Ordered Operations,mongodb在執行列表的寫入操作時,如果其中一個發生錯誤,它就會停止下來,不會在繼續執行列表內的其它寫入操作,並且前面的操作不會rollback 。 使用範例如下。 var bulk = db.collection.initializeOrderedBulkOp(); bulk.insert( { name: "mark"} ); bulk.insert( { name: "hoho"} ); bulk.execute(); Unordered Operations Unordered Operations,mongodb在執行列表的寫入操作時,如果其中一個發生錯誤,它不會停止下來,會繼續執行列表內的其它寫入操作,速度較快。 使用範例如下。 var bulk = db.collection.initializeUnorderedBulkOp(); bulk.insert( { name: "mark"} ); bulk.insert( { name: "hoho"} ); bulk.execute(); Ordered 與 Unordered我們在要如何選擇使用時機呢,記好只要有相關性的操作就要選擇用Ordered,而如果像是log之類的,流失一兩筆也是沒差,這時可以選用Unordered。 ~ 新增方法的效能測試 ~ 建立測試環境 首先我們先建立個新的資料夾,然後在裡面執行npm init來產生package.
安裝好MongoDB後,接下來本篇主要說明如何新增資料至MongoDB中,而用更精確的詞彙來說是,如何新增document至collection中。這邊我們會說明以下幾種MongoDB所的方法,來建立資料, 並說明這三種有何不同,而至於效能部份請看下篇~ Insert InsertOne InsertMany ~ Insert方法 ~ 單筆資料Insert insert函數可以將一個document建立到collection裡,我們這裡建立一個簡單的範例來看如何使用insert。 首先我們的需求是要建立一份使用者清單(collection),然後可以存放多筆使用者資料(document),我們假設使用者資料如下。 順到一提,mongodb自帶javascript shell,所以可以在shell執行javascript 一些語法。 user1 = { name : "Mark", age : 18, bmi : 10 } 然後我們要將這筆document新增至user的collection裡。 db.user.insert(user1); 新增完後,我們可以執行find指令,來查看user這collection中的資料。 db.user.find() 程式執行過程如下圖,而回傳值如下,代表成功新增一筆。 WriteResult({"nInserted" : 1}) 多筆資料Insert Insert函數同時也可以執行多筆,但效能好不好下篇會有比較。其中注意insert有個參數ordered ,true時代表如果其中一筆資料有問題,它就會停止下來,後面的資料都不會新增,而false時,則代表不會停下來,後面的資料會繼續新增,預設是true。 我們用下面範例來看看使用方法。 var user1 = { name : "Mark", age : 18, bmi : 10 }, count = 1000, users = []; for (var i=0;i<count;i++){ users.push(user1); } db.user.insert(users,{ordered:false}) 結果如下圖。 ~ InsertOne方法 ~ InsertOne函數事實上用法和insert差不多,只有兩點不同,首先是回傳,insertOne會回傳你所建立的document的ObjectId,ObjectId是系統自動生成的,是唯一值,而第二點不同就如同它的名字,他只能一次新增一筆。
由於網站上已經有很多mongodb的安裝方法,所以本篇將說明,如何使用Docker來建立可使用mongodb的環境,這也代表你的電腦只要有安裝docker,都可以使用mongodb,不再需要去找各種東西的安裝方法。 ~ Step1. 安裝Docker ~ Mac安裝 https://docs.docker.com/docker-for-mac/ docker最開始時還沒支援mac,而是需要用到其它方法來使用,但現在已經有出docker-for-Mac了,但注意雖然他是穩定版,但在mac自動休眠後,常常發生Bad response from Docker engine……,這目前好像沒啥解法,只能reset docker 或 重開機 …… Windows7 安裝 https://www.docker.com/products/docker-toolbox 雖然出了docker-for-windows但目前只支援windows10和Server 2016,windows7哭哭。 Windows10 安裝 https://docs.docker.com/docker-for-windows/ 懶講。 Ubuntu 安裝 https://philipzheng.gitbooks.io/docker_practice/content/install/ubuntu.html 請參考這篇安裝。 ~ Step2. 建立 docker-compose.yml ~ 在某個檔案夾下建立docker-compose.yml,並且內容如下,然後在執行docker-compose up指令,它就自動幫你建立一個裝有mongodb的環境。 version: '2' services: mongo: image: mongo ports: - "27017:27017" volumes_from: - mongodata mongodata: image: tianon/true volumes: - /data/db 下圖為在該檔案夾下執行docker-compose up結果。可以看到他建立一個port為27017並且資料存放在環境/data/db的mongodb。 ~ Step3. 進入Docker Container裡操作 MongoDB ~ 在執行完docker-compose up後,換到另一個shell,然後你可以執行docker ps指令來確定有mongodb的container有沒有執行,你可以把container想成為一個很小的VM。 從下圖可知,執行docker ps後可看到你這台電腦有在執行的container,其中mongo就是我們剛剛執行的。 接下來我們就執行docker exec -ti 333fba82b57e bash,其中333fba82b57e為CONTAINER ID,如下圖,你就進入到這個container中囉。
Hello ~ 大家好 ~ 接下來的30天的文章,小的我將要說明如何從0 → 1開始來學習MongoDB,咱們這30天的文章結構大至上會如下。 首先,先來個十篇的新手村之旅,大致上是說明MongoDB的基本操作CRUD。 再來開始進階一點,當我們上面十篇會基本上的使用MongoDB後,我們接下來就是要學習『如何用的好』,這時我們大概會花個六、七篇左右來說明說明。 然後我們這時要來『驗證』你上面的東西有沒有學會,我們大概會用個三篇來模擬個應用。記好『驗證』自已有沒有學會,是學習過程很重要的步驟,請別老是覺得看過懂了,就算學會,這種道理就像是你腦袋想的和寫出來的程式不見得會一樣,請記得寫測試驗證。 接下來就是進行分散式的章節,大概來個六~七篇。 最後就是一樣驗證你上面的東西有沒有學會。 上面大概就是這30天的簡略流程,那麼就開始吧。 由於是第一天,所以基本上就是要文言文一下,說明一下mongodb是啥。 什麼是MongoDB MongoDB的優缺與缺點 MongoDB的組成Document與Collection ~ 什麼是MongoDB ~ MongoDB一種強大,靈活、且易於擴展的文件導向式(document-oriented)資料庫,與傳統的關聯式導向資料庫相比,它不再有row的概念,取而代之的是document的概念,如下圖的fu。 ~ MongoDB的優缺與缺點 ~ 優點 Schema-less : MongoDB擁有非常彈性的Schema,這對RDBMS來說非常的難以高效能的方法來實現。 易於擴展 : MongoDB的設計採用橫向擴展,它的document的數據模型使寫能很容易在多台伺服器之間進行數據分割。 優透的性能 : MongoDB能預分配,以利用額外的空間換取穩定,同時盡可能把多的內存用作cache,試圖為每次查詢自動選擇正確的索引。 缺點 不支援事務操作 : 所以通常不適合應用在銀行或會計這種系統上,因為不包證一致性。 占用比較多空間 : 主要是有兩個原因,首先是它會預分配空間,為了提高效能,而第二個原因是欄位所占用的空間。 ~ MongoDB的組成 Document 與 Collection ~ Document Document是mongodb的核心,它就是Key對應個Value組合,例如下列範例。 { name : "mark". age : 100 , title : 'Mark BIG BIG' } document中的值可以是多種不同的類型,並且Key有幾個規定,首先它是區分大小寫,例如下面的範例這兩種是不同的,mongodb會存成兩份document。