MongoDB 將數(shù)據(jù)記錄存儲(chǔ)為 BSON類型的 文檔(document)魁蒜。 BSON 是一種二進(jìn)制數(shù)據(jù)類型,是json 的一種擴(kuò)展, bson 支持了更多的數(shù)據(jù)類型俄认。 下圖就是一個(gè)document(文檔) 示例:
1. 數(shù)據(jù)結(jié)構(gòu)
document 的數(shù)據(jù)結(jié)構(gòu)如下所示:由若干個(gè)字段-值對(duì)組成,其中字段的值可以是任何 BSON 數(shù)據(jù)類型洪乍,包括嵌入式數(shù)據(jù)結(jié)構(gòu)如 其他文檔眯杏、數(shù)組和文檔數(shù)組等。
{
field1: value1,
field2: value2,
field3: value3,
...
fieldN: valueN
}
如下申明了一個(gè)mydoc 的文檔壳澳,
var mydoc = {
_id: ObjectId("5099803df3f4948bd2f98391"),
name: { first: "Alan", last: "Turing" },
birth: new Date('Jun 23, 1912'),
death: new Date('Jun 07, 1954'),
contribs: [ "Turing machine", "Turing test", "Turingery" ],
views : NumberLong(1250000)
}
2. 字段命名
字段(field)的命名都是由字符串組成
字段名稱有以下幾條限制:
- 字段名
_id
用作主鍵岂贩;它的值在集合中必須是唯一的,是不可變的巷波,可以是數(shù)組以外的任何類型萎津。如果 _id 包含子字段,則子字段名稱不能以$
符號(hào)開(kāi)頭 - 字段名稱不能包含空字符
- 服務(wù)器允許存儲(chǔ)包含點(diǎn)
.
和美元符號(hào)$
開(kāi)頭的字段名稱 - MongodB 5.0 增加了對(duì)在字段名稱中使用
$
和.
的改進(jìn)支持抹镊。但有一些限制锉屈,具體如下:
在大多數(shù)情況下,無(wú)法直接訪問(wèn)使用此類字段名稱存儲(chǔ)的數(shù)據(jù)髓考。您需要在訪問(wèn)這些字段的查詢中使用$getField
部念、$setField
和$literal
等輔助方法
對(duì)于不同的字段類型的存儲(chǔ)操作,字段名稱驗(yàn)證規(guī)則并不相同。下面總結(jié)了不同的插入和更新操作如何處理以美元 ($) 為前綴的字段名稱
- MongoDB 不支持重復(fù)的字段名稱
2.1 插入操作
- 允許以
$
為前綴的字段作為插入的外層和嵌套字段名稱
// 示例 $price為外層字段
db.sales.insertOne( {
"$price": 50.00,
"quantity": 30
} )
- 使用其他保留關(guān)鍵字的插入允許使用
$
作為前綴儡炼。像 $inc 這樣的運(yùn)算符名稱可以用作字段名稱妓湘,也可以用作 id、db 和 ref 等保留關(guān)鍵字
db.books.insertOne( {
"$id": "h1961-01",
"location": {
"$db": "novels",
"$ref": "2007042768",
"$inc": true
} } )
- 在upset操作中 乌询,如果更新未找到執(zhí)行插入操作時(shí)榜贴,
upset
可以接受$
為前綴的字段, 但是如果更新的匹配到了文檔,此時(shí)妹田,更新操作可能會(huì)導(dǎo)致錯(cuò)誤唬党。 如下例子所示,$hotel
字段以$
開(kāi)頭鬼佣, 如果未匹配到date 為"2021-07-07" 的文檔驶拱,此時(shí)支持插入新的文檔, 但是如果匹配到了晶衷,此時(shí)更新操作失敗
db.expenses.updateOne(
{ "date": "2021-07-07" },
{ $set: {
"phone": 25.17,
"$hotel": 320.10
} },
{ upsert: true }
)
2.2 更新操作
- 文檔替代更新
文檔的更新要么新字段替換現(xiàn)有字段蓝纲,要么修改這些字段。在更新執(zhí)行替換的情況下晌纫,不允許以美元$
為前綴的字段作為最外層字段名稱税迷。
例如: 有如下的文檔,$rooms
字段是最外層字段,此時(shí)不允許對(duì)該字段的修改(不包括包括子文檔锹漱,子文檔中的字段可以箭养,例如br
和bath
),但是可以對(duì)其他非$
前綴的字段修改哥牍,如address 字段毕泌,包括address 字段中的子字段$number
和$street
修改
{
"_id": "E123",
"address": {
"$number": 123,
"$street": "Elm Road"
},
"$rooms": {
"br": 2,
"bath": 1
}
}
// 可以
db.expenses.updateOne(
{ "_id": "E123" },
{ $set: { "address.$street": "Elm Ave" } }
)
// 可以
db.expenses.updateOne(
{ "_id": "E123" },
{ $set: { "$rooms.br": "3222" } }
)
// 更新報(bào)錯(cuò)
db.expenses.updateOne(
{ "_id": "E123" },
{ $set: { "$rooms":{"bath":22,"bath":1111}}}
)
- 文檔的修改更新
當(dāng)更新修改而不是替換現(xiàn)有文檔字段時(shí),以$
為前綴的字段可以是最外層字段名稱砂心⌒复剩可以直接訪問(wèn)子字段,但您需要一個(gè)輔助方法來(lái)訪問(wèn)最外層字段
// 示例文檔
{
_id: ObjectId("610023ad7d58ecda39b8d161"),
"part": "AB305",
"$bin": 200,
"quantity": 100,
"pricing": { sale: true, "$discount": 60 }
}
修改非$
前綴的字段
db.inventory.findAndModify( {
query: { "part": { $eq: "AB305" } },
update: { $inc: { "pricing.$discount": 10 } }
} )
修改非$
前綴的最外層字段,通過(guò)literal 實(shí)現(xiàn)
db.inventory.findAndModify( {
query: { $expr: {
$eq: [ { $getField: { $literal: "$bin" } }, 200 ]
} },
update: { $inc: { "quantity": 10 } }
} )
- 文檔的聚合修改
在$replaceWith
與$setField
辩诞、$getField
和$literal
聯(lián)合使用來(lái)修改聚合管道中的$
前綴字段
// 示例文檔
{
"_id": 100001,
"$term": "fall",
"registered": true,
"grade": 4
}
使用管道創(chuàng)建一個(gè)名為spring2022的新集合坎弯,更新$
為前綴的 $term
字段
db.school.aggregate( [
{ $match: { "registered": true } },
{ $replaceWith: {
$setField: {
field: { $literal: "$term" },
input: "$$ROOT",
value: "spring"
} } },
{ $out: "spring2022" }
] )
3. 點(diǎn)符號(hào)
MongoDB 使用點(diǎn)符號(hào)來(lái)訪問(wèn)數(shù)組的元素和訪問(wèn)嵌入文檔的字段
3.1 數(shù)組
要通過(guò)從零開(kāi)始的索引位置指定或訪問(wèn)數(shù)組的元素,請(qǐng)將數(shù)組名稱與點(diǎn) (.) 和從零開(kāi)始的索引位置連接起來(lái)译暂,并用引號(hào)引起來(lái)
"<array>.<index>"
示例:
{
...
contribs: [ "Turing machine", "Turing test", "Turingery" ],
...
}
3.2 嵌入式文檔
要使用點(diǎn)表示法指定或訪問(wèn)嵌入文檔的字段抠忘,請(qǐng)將嵌入文檔名稱與點(diǎn) (.) 和字段名稱連接起來(lái),并用引號(hào)引起來(lái)
"<embedded document>.<field>"
示例:
{
...
name: { first: "Alan", last: "Turing" },
contact: { phone: { type: "cell", number: "111-222-3333" } },
...
}
- "name.last": 獲取name 字段中嵌入的子字段
- "contact.phone.number": 獲取contact 嵌入文檔中phone 中的子字段 number
4. 文檔限制
4.1 文檔大小
BSON 文檔的最大大小為 16 兆字節(jié)
最大文檔大小有助于確保單個(gè)文檔不會(huì)使用過(guò)多的 RAM外永,或者在傳輸期間不會(huì)使用過(guò)多的帶寬崎脉。為了存儲(chǔ)大于最大大小的文檔,MongoDB 提供了 GridFS API伯顶。
4.2 文檔字段有序性
與 JavaScript 對(duì)象不同囚灼,BSON 文檔中的字段是有序的
查詢操作時(shí)字段順序:
- 比較文檔時(shí)骆膝,字段排序很重要 例如 {a: 1, b: 1} 與 {b: 1, a: 1} 是不同的
- 為了高效的查詢執(zhí)行,查詢引擎可以在查詢處理期間重新排序字段灶体。在其他情況下阅签,處理下列運(yùn)算符時(shí)可能會(huì)發(fā)生重新排序字段:
$project
、$addFields
蝎抽、$set
和$unset
政钟。- 字段重新排序可能發(fā)生在中間結(jié)果以及查詢返回的最終結(jié)果中
- 由于某些操作可能會(huì)重新排序字段,因此不應(yīng)依賴使用前面列出的運(yùn)算符的查詢返回的結(jié)果中的特定字段排序樟结。
寫(xiě)操作時(shí)字段順序
對(duì)于寫(xiě)入操作养交,MongoDB 保留文檔字段的順序,但以下情況除外:
- _id 字段始終是文檔中的第一個(gè)字段
- 對(duì)字段的重新命名的更新操作(
$rename
)可能會(huì)導(dǎo)致文檔中的字段重新排序
4.3 _id字段
在 MongoDB 中瓢宦,存儲(chǔ)在集合中的每個(gè)文檔都需要一個(gè)唯一的 _id 字段作為主鍵碎连。如果插入的文檔省略了 _id 字段,MongoDB 驅(qū)動(dòng)程序會(huì)自動(dòng)為 _id 字段生成一個(gè) ObjectId刁笙。 這也適用于通過(guò)帶有 upsert: true 的更新操作插入的文檔
_id 字段具有以下限制:
- 默認(rèn)情況下破花,MongoDB 在創(chuàng)建集合期間會(huì)在 _id 字段上創(chuàng)建唯一索引
- _id 字段始終是文檔中的第一個(gè)字段。如果服務(wù)器先接收到一個(gè)沒(méi)有_id字段的文檔疲吸,那么服務(wù)器會(huì)自動(dòng)生成該字段并將該字段移到開(kāi)頭
- 如果 _id 包含子字段,則子字段名稱不能開(kāi)頭 帶有 ($) 符號(hào)
- _id 字段可以包含任何 BSON 數(shù)據(jù)類型的值前鹅,除了數(shù)組摘悴、正則表達(dá)式和未定義類型
_id 字段常用值:
- 使用ObjectId 類型
- 使用自然唯一標(biāo)識(shí)符。這節(jié)省了空間并避免了額外的索引
- 生成一個(gè)自動(dòng)遞增的數(shù)字
- 在您應(yīng)用程序代碼中生成一個(gè) UUID舰绘, 并將UUID以BinData 類型存儲(chǔ)蹂喻。 使得在集合和 _id 索引中更有效地存儲(chǔ) UUID 值