本文主要記錄近期學(xué)習(xí) MongoDB 的一些內(nèi)容,主要參考了官方文檔 https://docs.mongodb.com/v4.4/,MongoDB 的官方文檔寫的還是挺不錯的,很詳細(xì),值得一看。
本文包含以下幾部分內(nèi)容:
- 認(rèn)識 MongoD
- 文檔的 CRUD 操作
- 聚合操作
- 索引
- 事務(wù)
- 副本集
下邊 MongoDB 的相關(guān)操作都是在 Mongo Shell 中執(zhí)行的,這種方式不需要考慮語言環(huán)境的問題,具有一定的通用性,也足以應(yīng)對一些不是特別復(fù)雜的操作,作為 MongoDB 入門也足夠了。
一、認(rèn)識 MongoD
MongoDB 屬于 NoSQL 類型數(shù)據(jù)庫的一種,它具有高性能、高可用、易擴(kuò)展等特點。
MongoDB 是一個文檔數(shù)據(jù)庫,它以 Binary JSON(BSON) 的數(shù)據(jù)結(jié)構(gòu)來存儲文檔格式的數(shù)據(jù),簡單的說一條文檔數(shù)據(jù)就類似于 JSON 對象,即鍵值對的形式,借用官方的以一張圖來認(rèn)識下文檔數(shù)據(jù):

其中 field 是字符串類型,但是 value 支持的數(shù)據(jù)類型相比傳統(tǒng)的 JSON 更加豐富,后邊我們細(xì)聊。
為了直觀的認(rèn)識一下 MongoDB,我們來對比常見的關(guān)系型數(shù)據(jù)庫 MySQL,兩者還是有很多相似的地方:
| MySQL | MongoDB |
|---|---|
| Database(數(shù)據(jù)庫) | Database(數(shù)據(jù)庫) |
| Table(表) | Collection(集合) |
| Row(行) | Document(文檔) |
| Column(列) | Field(字段) |
| Primary Key(主鍵) | Primary Key(主鍵) |
| Index(索引) | Index(索引) |
| Transactions(事務(wù)) | Transactions(事務(wù)) |
1.1、一些特點
每一種數(shù)據(jù)庫都有自身相對適用的場景,如果我們的需求大致符合以下的場景,就可以考慮使用 MongoDB,這樣可用更低的成本來解決問題:
- 需要支持事務(wù),從 MongoDB4.0 版本開始已經(jīng)逐步完善了對事務(wù)的支持,開始支持多文檔事務(wù),通過分布式事務(wù),事務(wù)可以跨多個操作、集合、數(shù)據(jù)庫、文檔和分片使用。
- 不需要復(fù)雜的多表關(guān)聯(lián)查詢(join),在 MongoDB 中使用 Aggregation Pipeline 中的 $lookup 操作符可以簡單的實現(xiàn) left outer join。
- 對文本以及地理位置數(shù)據(jù)的存儲、查詢有較強(qiáng)烈的需求。當(dāng)然特別專業(yè)的文本檢索還是需要用搜素引擎的,比如 Elasticsearch
- 數(shù)據(jù)模型可能有比較頻繁的變更
- 有高并發(fā)的數(shù)據(jù)讀寫需求
- 需要存儲大規(guī)模的數(shù)據(jù)
- 對數(shù)據(jù)庫的擴(kuò)展性以及可用性有較高的需求
1.2、常用數(shù)據(jù)類型
也就是 BSON 中支持的數(shù)據(jù)類型:https://docs.mongodb.com/manual/reference/bson-types/
| 類型 | 說明 |
|---|---|
| ObjectId | 是一個 12 字節(jié)的 16 進(jìn)制數(shù),每個字節(jié)占兩位,總長度為 24 位。具體是由 4 字節(jié)的時間戳(以 Unix 紀(jì)元以來的秒數(shù)為單位)、5 字節(jié)的根據(jù)機(jī)器和進(jìn)程生成的唯一隨機(jī)值、3 字節(jié)的遞增計數(shù)器值(使用前邊的隨機(jī)值作為起始值)組成。每條文檔數(shù)據(jù)都有一個默認(rèn)字段_id作為主鍵,如果沒有指定值,MongoDB 會指定一個 ObjectId 類型的默認(rèn)值 |
| String | 必須是 UTF-8 編碼格式的字符串 |
| Double | 浮點數(shù),MongoDB 中沒有 Float 類型 |
| Decimal128 | 高精度浮點數(shù),可用于存儲價格等數(shù)據(jù) |
| Integer | 分為 32 位(NumberInt)和 64(NumberLong)位兩種整數(shù) |
| Boolean | true 或者 false |
| Object | 對象,可用于嵌入另外一個文檔 |
| Null | 存儲空值 |
| Array | 用于存儲數(shù)組或者列表 |
| Date | 日期,是一個 64 位整數(shù),從 1970.1.1(Unix 紀(jì)元)到現(xiàn)在的毫秒數(shù), 也稱為 UTC 日期時間,推薦使用 Date 類型處理日期 |
| Timestamp | 時間戳,64位,前 32 位是從 1970.1.1(Unix 紀(jì)元)到現(xiàn)在的秒數(shù),后 32 位是給定秒內(nèi)操作的遞增序數(shù),無法與 Date 類型關(guān)聯(lián),主要供內(nèi)部 MongoDB 使用,在單個 mongod 實例中,時間戳值始終是唯一的 |
| Regular Expression | 存儲正則表達(dá)式 |
| Code | 存儲 JavaScript 代碼 |
| Binary Data | 存儲二進(jìn)制數(shù)據(jù) |
1.3、數(shù)據(jù)庫、集合、文檔
https://docs.mongodb.com/v4.4/core/databases-and-collections/
MongoDB 將數(shù)據(jù)記錄以文檔(Document)的形式保存集合(Collection)中、集合又需要存儲到對應(yīng)的數(shù)據(jù)庫(Database)中,所以掌握它們的一些常用操作。
MongoDB 中對數(shù)據(jù)庫、集合、文檔 field 等的命名規(guī)范以及長度都有一定的限制,可以參考官方文檔查看 。
MongoDB 的安裝可以參考文章最后一部分,起動安裝的 MongoDB 節(jié)點,然后連接到主節(jié)點:
mongo -u <user> -p <pass> --host <host> --port <port>
除了mongo之外,其它參數(shù)都是可選的,host、port 默認(rèn)是本機(jī)的27017端口。
1.3.1、創(chuàng)建、切換數(shù)據(jù)庫
use mydb
通過use 命令可以切換到指定的已存在的數(shù)據(jù)庫,如果數(shù)據(jù)庫不存在,則會在第一次給數(shù)據(jù)庫創(chuàng)建集合時創(chuàng)建對應(yīng)的數(shù)據(jù)庫。
1.3.2、查看所有的數(shù)據(jù)庫
show dbs
show databases
上邊兩個命令都可以查看權(quán)限內(nèi)的所有數(shù)據(jù)庫。
1.3.3、查看當(dāng)前所在的數(shù)據(jù)庫
db
1.3.4、刪除數(shù)據(jù)庫
db.dropDatabase()
切換到指定數(shù)據(jù)庫后,才可以刪除數(shù)據(jù)庫。
1.3.5、查看數(shù)據(jù)庫中的所有集合
show collections
先切換到指定數(shù)據(jù),才可以查看其中的集合。
1.3.6、創(chuàng)建集合
db.createCollection("book")
這種屬于指定集合名稱顯式的創(chuàng)建方式,需要先切換到指定數(shù)據(jù)庫。
除了顯式的創(chuàng)建集合,還在隱式創(chuàng)建,如果集合不存在,當(dāng)?shù)谝煌现斜4鏀?shù)據(jù)時就會自動創(chuàng)建對應(yīng)的集合,比如:
db.book.inserOne({})
1.3.7、刪除集合
db.book.drop()
切換到指定數(shù)據(jù)庫后,就可以根據(jù)名稱來刪除集合。
二、文檔的 CRUD 操作
https://docs.mongodb.com/v4.4/crud/
2.1、插入
2.1.1、insertOne()
insertOne()是給指定集合中插入單條文檔,如果文檔沒有指定_id字段,則 MongoDB 會添加一個值為 ObjectId _id 字段到文檔中,注意 _id 字段為默認(rèn)主鍵。
db.book.insertOne({
"author": "劉慈欣",
"commentCount": 1038006,
"img": "http://img14.360buyimg.com/n1/s200x200_jfs/t1705/189/702227414/177982/cc8c12f0/55dab54dN5271c377.jpg",
"name": "中國科幻基石叢書:三體(套裝1-3冊)",
"price": 83.7,
"publisher": "重慶出版社",
"shop": "科幻世界京東自營店",
"skuId": "11757834"
})
插入數(shù)據(jù)時如果不指定字段的類型,則 MongoDB 會使用自推斷出數(shù)據(jù)類型來存儲數(shù)據(jù),但這可能不是你需要的理想類型。
2.1.2、insertMany()
如果要一次插入多條文檔,可以使用insertMany(),并以數(shù)組形式傳入文檔,_id字段的處理規(guī)則和上邊一致。
db.book.insertMany([
{
"author": "紫金陳",
"commentCount": 36164,
"img": "http://img14.360buyimg.com/n1/s200x200_jfs/t1/130390/12/10177/391461/5f63788cE9b818814/fa5465bd1280778e.jpg",
"name": "長夜難明(紫金陳社會派懸疑推理小說必讀 迷霧劇場廖凡白宇出演的網(wǎng)劇《沉默的真相》原著小說)",
"price": 42,
"publisher": "云南人民出版社",
"shop": "浦睿文化京東自營店",
"skuId": "12081064"
},
{
"author": "魯迅",
"commentCount": 25567,
"img": "http://img11.360buyimg.com/n1/s200x200_g15/M0A/0F/18/rBEhWFJeUd4IAAAAAAbFh_6HtZsAAENlQKKQEoABsWf482.jpg",
"name": "吶喊",
"price": 10.6,
"publisher": "譯林出版社",
"shop": "鳳凰壹力京東自營店",
"skuId": "11338556"
}
])
2.1.3、insert()
insert()既可以用來給集合中插入單條文檔,也可以通過數(shù)組參數(shù)插入多條文檔。
db.book.insert({})
db.book.insert([{}, {}])
2.2、查詢
查詢主要是通過find(query, projection)方法來實現(xiàn)的,find()會返回符合條件的全部文檔,但需要注意在 mongo shell 中并不是一次性返回集合中的全部文檔。
比如下邊的查詢(沒有查詢條件{}可以省略):
db.book.find({})
該查詢實際會返回一個游標(biāo)(cursor),但我們沒有通過定義var變量接收游標(biāo),所以游標(biāo)會自動迭代最多 20 次,所以該查詢最多返回 20 條文檔。
如果我們通過變量來接收返回的游標(biāo),并手動調(diào)用游標(biāo)變量,同樣游標(biāo)會自動迭代 20 次,并返回對應(yīng)的文檔:
> var c = db.book.find({})
> c
> c
當(dāng)然也可以手動迭代,逐條返回數(shù)據(jù):
> var c = db.book.find({})
> while (c.hasNext()) {
printjson(c.next());
}
這個默認(rèn)的 20 次可以通過如下方式修改:
DBQuery.shellBatchSize = 30
MongoDB 中提供了許多查詢相關(guān)的操作符,來滿足不同的場景,
2.2.1、比較運(yùn)算符
比較運(yùn)算符有$eq、$gt、$gte、$lt、$lte、$ne、$in、$nin,我們來看幾個例子
比如查詢author的是紫金陳的文檔:
db.book.find({author: {$eq: "紫金陳"}})
也可以使用下邊的簡化寫法:
db.book.find({author: "紫金陳"})
但需要注意,如果使用$eq時查詢條件的值是正則表達(dá)式,那么只會匹配字段值也是正則表達(dá)式的文檔,比如下邊的模糊查詢是沒有結(jié)果的:
db.book.find({author: {$eq: /金/}})
需要使用如下的查詢方式才能查詢到author中包含金的文檔:
db.book.find({author: /金/})
db.book.find({author: {$regex: /金/}})
查詢price小于100的書:
db.book.find({price: {$lt: 100}})
查詢author是紫金陳或者魯迅的文檔:
db.book.find({author: {$in: ["紫金陳", "魯迅"]}})
這個功能也可以使用邏輯運(yùn)算符$or實現(xiàn),但這種場景還是建議使用$in
詳細(xì)內(nèi)容可參考文檔:https://docs.mongodb.com/v4.4/reference/operator/query-comparison/
2.2.2、邏輯運(yùn)算符
邏輯運(yùn)算符有$and、$or、$not、$nor。
比如查詢name是吶喊并且price小于等于50的文檔,可以使用$and:
db.book.find({$and: [{name: "吶喊"}, {price: {$lte: 50}}]})
還有一種等價簡單的寫法,提供了隱式的$and操作:
db.book.find({name: "吶喊", price: {$lte: 50}})
查詢price小于 100 或者commentCount大于100000的文檔,可以使用$or:
db.book.find($or: [{price: {$lt: 100}}, {commentCount: {$gt: 100000}}])
$not表示邏輯非操作,即對查詢條件取反,同時也會查詢不包含對應(yīng)字段的文檔,如下查詢語句:
db.book.find({price: {$not: {$lt: 100}}})
會查詢price大于 100 或者不包含price的文檔。
$nor會返回不匹配所有指定字段的查詢條件的文檔,比如:
db.book.find($nor: [{author: "魯迅"}, {commentCount: {$gt: 100000}}])
會查詢author不是魯迅、commentCount不大于 100000、以及不包含對應(yīng)字段的文檔。
2.2.3、文檔字段與查詢
2.2.3.1
find()方法默認(rèn)會返回文檔的全部字段,我們可以通過它的第二個參數(shù)來指定只返回哪些字段和不返回那些字段。
db.book.find({}, {name: 1})
理論上我們希望返回的文檔只有name字段,但其實也包含_id字段,因為_id字段比較特殊我們可以手動指定不返回:
db.book.find({}, {name: 1, _id: 0}
所以1表示只會返回對應(yīng)的字段,其它字段不返回(_id需要單獨(dú)指定);0表示不會返回對應(yīng)的字段,其它字段不受影響。
2.2.3.2
有時我們需要查詢存在某個字段的文檔,可以使用$exists:
db.book.find({img: {$exists: true}})
上邊會查詢有img字段的文檔。
2.2.3.3
還可以按照字段類型還查詢文檔數(shù)據(jù),$type可以用來查詢字段值為指定 BSON 類型的文檔:
db.book.find({commentCount: {$type: "long"}})
也可以用數(shù)組表示多個類型:
db.book.find({commentCount: {$type: ["long", "string"]}})
2.2.3.4
要獲取一個字段的類型,可以使用typeof:
typeof db.book.name
要判斷一個字段是否為指定類型,可以使用instanceof:
db.book.commentCount instanceof NumberInt
2.2.3.5
mongo 中關(guān)于null值的查詢比較特殊,如下查詢會返回img字段值為null以及不存在該字段的文檔:
db.book.find(img: null)
2.2.4、排序
find()方法查詢到的結(jié)果默認(rèn)按_id升序排列,我們也可以使用sort()方法按照指定字段來排序。
db.book.find().sort({price: 1})
price: 1表示查詢結(jié)果按照price升序排列,1表示升序、-1表示降序。
也可以同時用多個字段排序:
db.book.find().sort({price: 1, commentCount: -1})
2.2.5、分頁
分頁查詢一般通過limit()、skip()兩個方法結(jié)合實現(xiàn):
- limit: 控制每次查詢返回的文檔數(shù)量
- skip: 從查詢結(jié)果中跳過指定數(shù)量的文檔再開始返回
db.book.find().sort({price: 1}).limit(10).skip(20)
從查詢結(jié)果中跳過20條數(shù)據(jù)后取10條數(shù)據(jù)返回,即第三次分頁查詢。
這種方式用起來簡單,但是當(dāng)數(shù)據(jù)量比較大的時候,比如幾十萬到一百萬左右,越往后分頁查詢就越慢,有比較明顯的性能問題。所以不太適合大量數(shù)據(jù)的全量分頁查詢,只查詢少量數(shù)據(jù)還是可以的。
當(dāng)然在實際中也是有大量數(shù)據(jù)的全量分頁查詢需求的,此時我們可以指定一個排序字段,保證字段值按一定的規(guī)律增長即可。這樣每次查詢時需要攜帶一個排序字段的基準(zhǔn)值,查詢前先按照排序字段排序,再查詢排序字段值大于或小于該基準(zhǔn)值的指定數(shù)據(jù)量數(shù)據(jù)。
2.3、更新
更新文檔的方法可以指定三個參數(shù):
-
filter,文檔的過濾條件也就是要更新哪些文檔 -
update,如何更新文檔 -
options, 可選參數(shù),指定一些額外的屬性
具體的可以參考文檔 https://docs.mongodb.com/v4.4/reference/update-methods/
比如要更新匹配過濾條件的第一條文檔,可以使用updateOne():
db.book.updateOne({skuId: "11757834"}, {$set: {price: 66.6}, $currentDate: {lastModified: true}}, {upsert: true})
{skuId: "11757834"}是過濾條件,$set操作符用來更新指定字段的值。$currentDate操作符用來將lastModified字段的值更新為當(dāng)前時間(默認(rèn) Date)(如果該字段不存在則會自動創(chuàng)建)。upsert是可選屬性,要更新的文檔不存在時決定是否插入新文檔。
updateMany()和updateOne()用法類似,但是會更新匹配條件的全部文檔,比如將指定作者的書調(diào)價:
db.book.updateMany({author: "魯迅"}, {$inc: {price: 2}})
還有一個update()方法,可以通過指定第三個參數(shù)的multi屬性來決定是更新單個文檔還是多個文檔。
除了上邊用的$set、$currentDate、$inc這幾個更新操作符之外,其它的可以參考文檔 https://docs.mongodb.com/manual/reference/operator/update/
最后還有一個replaceOne()方法,可以用新文檔替換掉符合過濾條件的第一個文檔。
2.4、刪除
刪除操作相對簡單一些,根據(jù)指定的過濾條件,可以一次最多刪除一條,也可以刪除全部。
deleteOne()每次最多刪除一條文檔:
db.book.deleteOne({author: "魯迅"})
deleteMany()則可以刪除全部符合條件的。
還有一個remove()方法,可以通過justOne屬性指定一次刪除最多一條還是全部:
db.book.remove({author: "魯迅"}, {justOne: false})
三、聚合操作
除了一般的查詢操作,MongoDB 提供的聚合查詢也是非常有用的,主要可以實現(xiàn)對文檔數(shù)據(jù)的統(tǒng)計、分析功能。這里我們主要了解一下聚合管道,至于Map-Reduce就不討論了,它基本可以由聚合管道代替,同時可以獲得更高的性能以及可以用性。最后還有一些簡單的聚合操作方法可以了解下,方便處理一下簡單的需求。
詳細(xì)的內(nèi)容可以直接參考官方文檔 Aggregation。
3.1、聚合管道
聚合管道,類似于流水線處理,對輸入的文檔進(jìn)行一個或者多個階段的連續(xù)處理,上一個處理階段的輸出作為下一個階段的輸入,直到所有階段全部完成后輸出最終的處理結(jié)果。
先看一個例子來感受一下這個過程,比如先過濾出commentCount大于 100000 的文檔、再按author字段分組并統(tǒng)計出組內(nèi)總的文檔數(shù)bookCount、然后按照bookCount降序排列:
db.book.aggregate([
{$match: {commentCount: {$gt: 100000}}},
{$group: {_id: "$author", bookCount: {$sum: 1}}},
{$sort: {bookCount: -1}}
])
經(jīng)過上邊三個階段的處理,最終的結(jié)果如下:

在聚合管道中,每一個階段(Stage)都需要指定一個操作符來做具體的事情,比如前邊的$match、$group、$sort。目前官方提供了大約 30 個階段操作符,具體的可以參考文檔 Aggregation Pipeline Stages,每個操作符都有詳細(xì)的用法。
這里簡單介紹一下,知道每個操作符大概是做什么的,可以在那些場景使用:
-
$match,從中的文檔數(shù)據(jù)中過濾出符合查詢條件的 -
$group,對文檔按指定字段進(jìn)行分組,字段的值相同的會被分為一組,同時可以對組內(nèi)數(shù)據(jù)進(jìn)行各種計算操作,最后為每組輸出一個文檔,分組也可以實現(xiàn)數(shù)據(jù)去重的效果 -
$bucket,功能和$group類似也會對文檔分組,但是它可以指定分組字段的值的區(qū)間,對應(yīng)區(qū)間的文檔會被分為一組 -
$sort,對文檔按指定字段排序,可以指定多個字段,-1降序、1升序 -
$count,返回管道中當(dāng)前階段的文檔數(shù) -
$sortByCount,對輸入的文檔按指定字段的值進(jìn)行分組,然后計算每組的文檔數(shù),相當(dāng)于$group+$sort -
$skip,跳過指定個數(shù)的文檔,將剩余的傳遞到下一階段 -
$limit,指定傳遞到下一階段的文檔數(shù) -
$project,給文檔添加新字段或者刪除已有字段,即重構(gòu)文檔結(jié)構(gòu) -
$addFields,給文檔中添加新字段,它還有一個別名$set,和$project的添加功能類似 -
$unset,從文檔中刪除已有的字段,和$project的刪除功能類似 -
$unwind,對文檔中的數(shù)組類型字段進(jìn)行結(jié)構(gòu)操作,即將數(shù)組值拆分成多個元素,每個元素值將會替換原數(shù)組的值并且和當(dāng)前文檔的其它字段組成一個新的文檔 -
$lookup,主要的功能是兩個集合之間的關(guān)聯(lián)查詢,類似于 MySQL 中的 left join -
$unionWith,可以將兩個集合的管道處理結(jié)果集組合成一個新的結(jié)果集(不會去重),然后輸入到管道中的下一階段 -
$out,必須出現(xiàn)在聚合管道的最后一個階段,將前邊管道返回的文檔寫入指定的集合中
3.2、簡單的聚合操作
簡單的聚合操作主要是針對單個集合中文檔的操作,功能都很簡單。
前幾個都是統(tǒng)計集合中文檔數(shù)的。
3.2.1、count()
db.book.count({author: "魯迅"})
使用count()方法時盡量不要省略過濾條件,否則會使用元數(shù)據(jù)來計數(shù),在分片集群上、非常正關(guān)機(jī)可能導(dǎo)致結(jié)果不準(zhǔn)確。
上邊的計數(shù)統(tǒng)計也等價于:
db.book.find({author: "魯迅"}).count()
除了設(shè)置過濾條件,還支持可選的參數(shù),比如設(shè)置最多統(tǒng)計的文檔數(shù),避免統(tǒng)計過多的數(shù)據(jù)做無用功:
db.book.count({author: "魯迅", commentCount: {$gt: 100000}}, {limit: 10000})
3.2.2、estimatedDocumentCount()
db.book.estimatedDocumentCount()
這個不方法支持過濾條件,它直接使用元數(shù)據(jù)來計數(shù),也存在上邊count()方法的問題
3.2.3、countDocuments()
db.book.countDocuments({author: "魯迅"})
這個方法支持過濾條件以及一些可以選的參數(shù)(比如limit),它不會使用元數(shù)據(jù)統(tǒng)計文檔數(shù)量,而是通過文檔的聚合返回準(zhǔn)確的統(tǒng)計結(jié)果。而且沒有上邊兩個方法存在的問題。
3.2.4、distinct()
在單個集合中根據(jù)指定的過濾條件查找指定字段的不同值,類似于去重,并且以數(shù)組形式返回字段的值,比如
db.book.distinct("author", {commentCount: {$gt: 100000}})
四、索引
4.1、認(rèn)識索引
MongoDB 中的索引和 MySQL 中的類似,MongoDB 中使用索引可以高效的進(jìn)行數(shù)據(jù)查詢,如果不使用索引,查詢過程中就必須掃描集合中的每個文檔,進(jìn)而找到匹配查詢條件的,有了索引了,則會從索引中找到匹配查詢條件的文檔。
MongoDB 中的索引是集合級別的,可以給文檔中任何字段(包括嵌套的子字段)創(chuàng)建索引,索引使用 B-tree 的數(shù)據(jù)結(jié)構(gòu),它會存儲作為索引的字段的值,并且按照字段值升序或降序排列,來提高查詢的效率。
借用官方的一張圖來看一下使用索引時的查詢過程:
4.2、創(chuàng)建索引
MongoDB 在創(chuàng)建集合時,默認(rèn)會在_id字段上創(chuàng)建唯一索引,索引名為_id_并且該索引不能被刪除,這樣也可以保證_id字段值相同的文檔不會被重復(fù)插入到集合中。
除了默認(rèn)的_id字段上的索引,我們也可以使用createIndex()方法給其它字段創(chuàng)建索引。
首先可以給單個字段創(chuàng)建索引,比如:
db.book.createIndex({skuId: -1}, {name: "skuIdIndex"})
上邊給
author字段創(chuàng)建了索引,-1表示創(chuàng)建降序索引,即索引中的值按降序排列,1則表示升序索引,對于單字段索引,使用降序或升序都可以,因為 MongoDB 可以從任意一個方向來遍歷索引。name則用來指定索引的名稱,也可以直接使用默認(rèn)值,索引的默認(rèn)名稱由索引鍵(指定的字段名)和索引鍵方向(-1或1)使用下劃線拼接成,上邊例子的默認(rèn)索引名就是skuId_-1。
最后可以查看創(chuàng)建好的索引:

此時也可以看到 MongoDB 在_id字段上創(chuàng)建的默認(rèn)索引。
除了上邊的單字段索引,還有復(fù)合索引,使用復(fù)合索引可以同時在多個字段上創(chuàng)建索引,此時字段之間的順序很重要,這也決定了索引中數(shù)據(jù)排序的先后順序。如下創(chuàng)建一個復(fù)合索引:
db.book.createIndex({auther: 1, name: 1})
這里沒有指定索引名稱,而是使用默認(rèn)的,查看創(chuàng)建好的索引:

五、事務(wù)
在 MongoDB4.0 版本之前, 僅對單個文檔的操作是具有原子性的,即單文檔事務(wù),這里因為 MongoDB 中文檔是可以嵌套子文檔的,這樣就解決了一般關(guān)系型數(shù)據(jù)庫需要多張表才能描述不同數(shù)據(jù)之間關(guān)系的問題,所以這樣也能在一定程度上保證數(shù)據(jù)的一致性。
但是對于同一集合或者不同集合甚至不同數(shù)據(jù)庫中的多個文檔的讀寫就不能保證原子性了,但這個需求顯然也是存在的,所以從 MongoDB4.0 開始支持多文檔事務(wù)了:
- 在 MongoDB4.0 版本中,支持副本集上的多文檔事務(wù)
- 在 MongoDB4.2 版本中,新增了分片集群上多文檔事務(wù)的支持,同時也合并了副本集上多文檔事務(wù)的支持。從 MongoDB4.2 版本開始
多文檔事務(wù)也稱為分布式事務(wù)。
在大多數(shù)情況下,多文檔事務(wù)比單文檔事務(wù)有著更大的性能開銷,所以盡可能的去優(yōu)化文檔的結(jié)構(gòu),使用單文檔事務(wù),而不是過分的依賴多文檔事務(wù)。
MongoDB 在單節(jié)點模式下是不支持事務(wù)的,所以要使用 MongoDB 的事務(wù)特性就需要搭建副本集(Replica Set)或者分片集群(Sharded Cluster)
關(guān)于事務(wù)這里簡單的介紹一下,更多內(nèi)容還請參考官方文檔 https://docs.mongodb.com/v4.4/core/transactions/
六、副本集
副本集是一組維護(hù)相同數(shù)據(jù)集的 Mongod 實例,具有自動的故障恢復(fù)、讀寫分離等特點,其中包含一個主節(jié)點(Primary)、一個或多個副本節(jié)點(Secondary)、一個可選的仲裁節(jié)點(Arbiter)。
- 主節(jié)點負(fù)責(zé)處理所有的寫操作,也可以處理查詢操作
- 副本節(jié)點會作從主節(jié)點同步數(shù)據(jù),不處理寫操作,但可以通過修改客戶端連接支持讀操作來減少主節(jié)點的壓力,主節(jié)點掛掉時會參與主節(jié)點的選舉、投票
- 沖裁節(jié)點不存儲數(shù)據(jù),不參與主節(jié)點的選舉,但是會參與投票,一般會在硬件成本有限的情況下使用仲裁節(jié)點來代替副本節(jié)點,同時也保證了副本集的正常工作。
關(guān)于副本集詳細(xì)的介紹可以參考 https://docs.mongodb.com/manual/replication/
搭建副本集可以采用 一個主節(jié)點+偶數(shù)個副本節(jié)點 或者 一個主節(jié)點+奇數(shù)個副本節(jié)點+一個仲裁節(jié)點 的方式,這里簡單介紹在 Winddows 環(huán)境下采用1主1副本1仲裁的組合模擬搭建副本集。
這里下載 MongoDB4.4.10 版本 zip 包,解壓3份,如下:

分別在3個根目錄下創(chuàng)建conf、data\db、log目錄,然后在conf、log分別創(chuàng)建mongod.conf、mongod.log文件。
下邊是mongodb-1中mongod.conf的內(nèi)容:
storage:
dbPath: D:\mongodb-4.4.10\mongodb-1\data\db # mongod實例的數(shù)據(jù)存儲目錄
journal:
enabled: true # 啟用持久性日志以確保數(shù)據(jù)文件保持有效和可恢復(fù)
systemLog:
destination: file
path: D:\mongodb-4.4.10\mongodb-1\log\mongod.log # 指定日志文件
logAppend: true # 重啟時將日志追加到現(xiàn)有之日文件末尾
net:
bindIp: localhost # 綁定ip,默認(rèn)就是localhost
port: 27017 # 綁定的端口號,默認(rèn)就是27017
replication:
replSetName: myrs #副本集的名稱
其它兩個分別修改dbPath、path、port即可。mongodb-2、mongodb-3的端口分別指定為27018、27019
配置完后,分別在bin目下啟動3個節(jié)點:
mongod -f ../conf/mongod.conf
連接到mongodb-1(也可以連接到其它節(jié)點,可以在bin目錄下啟動也可以配置環(huán)境變量后在任意目錄啟動),進(jìn)行副本集初始化:
mongo --host localhost --port 27017
rs.initiate()
初始化成功后,mongodb-1默認(rèn)會成為主節(jié)點,向副本集中添加副本節(jié)點mongodb-2:
rs.add("localhost:27018")
還需要添加仲裁節(jié)點mongodb-3:
rs.addArb("localhost:27019")
連接到上邊添加的副本節(jié)點,使其支持讀操作:
mongo --host localhost --port 27018
rs.secondaryOk()
到這里基本的配置就完成了,向主節(jié)點添加數(shù)據(jù)后,副本節(jié)點會自動同步主節(jié)點的數(shù)據(jù),如果主節(jié)點掛掉則會從副本節(jié)點中重新選舉新的主節(jié)點,后期重新啟動副本集的話,也會選舉新的主節(jié)點。
本文內(nèi)容到這里就結(jié)束了,下一篇我們將學(xué)習(xí)如在 Springboot 中使用 MongoDB。