Mongodb基礎(chǔ)知識

1. 介紹缔莲、安裝、使用(簡單寫寫扎唾,不做詳細(xì)介紹)

1.1 介紹

  1. Mongodb是屬于NoSql的一種數(shù)據(jù)類型召川;
  2. MongoDB 是一個介于關(guān)系數(shù)據(jù)庫和非關(guān)系數(shù)據(jù)庫之間的產(chǎn)品,是非關(guān)系數(shù)據(jù)庫當(dāng)中功能最豐富胸遇,最像關(guān)系數(shù)據(jù)庫的荧呐;
  3. 它支持的數(shù)據(jù)結(jié)構(gòu)非常松散,是類似 json 的 bson 格式纸镊,因此可以存儲比較復(fù)雜的數(shù)據(jù)類型坛增;
  4. Mongo 最大的特點(diǎn)是他支持的查詢語言非常強(qiáng)大,其語法有點(diǎn)類似于面向?qū)ο蟮牟樵冋Z言薄腻,幾乎可以實(shí)現(xiàn)類似關(guān)系數(shù)據(jù)庫單表查詢的絕大部分功能收捣,而且還支持對數(shù)據(jù)建立索引;
  5. 它的特點(diǎn)是高性能 庵楷、易部署 罢艾、易使用 ,存儲數(shù)據(jù)非常方便 尽纽。

1.2 安裝

MacOS中推薦使用HomeBrew安裝咐蚯,簡單,沒啥理由弄贿。
安裝步驟:https://docs.mongodb.com/manual/tutorial/install-mongodb-on-os-x/

1.3 使用

2. Databases春锋、Collections和Document

noSql是無模式的文檔數(shù)據(jù)庫

mongodb sqlServer
database => database
collection => table
document => row

Note:

  • row: 每一行都是一樣的字段,不可添加不可減少差凹,即field的個數(shù)在定義table時就已經(jīng)聲明好了

  • document: 每一個document都是獨(dú)立的期奔,也不是在collection定義的時候聲明好的。

    SQL VS NoSQL.png

3. Bson結(jié)構(gòu)解析以及$type和_id

3.1 Bson支持的數(shù)據(jù)類型

BSON is a binary serialization format used to store documents and make remote procedure calls in MongoDB.

Type Number Alias Notes
Double 1 “double”
String 2 “string”
Object 3 “object”
Array 4 “array”
Binary data 5 “binData”
Undefined 6 “undefined” Deprecated.
ObjectId 7 “objectId”
Boolean 8 “bool”
Date 9 “date”
Null 10 “null”
Regular Expression 11 “regex”
DBPointer 12 “dbPointer” Deprecated.
JavaScript 13 “javascript”
Symbol 14 “symbol” Deprecated.
JavaScript (with scope) 15 “javascriptWithScope”
32-bit integer 16 “int”
Timestamp 17 “timestamp”
64-bit integer 18 “l(fā)ong”
Decimal128 19 “decimal” New in version 3.4.
Min key -1 “minKey”
Max key 127 “maxKey”

3.2 $type

可以使用$type操作符根據(jù)上表中列出的BSON數(shù)據(jù)類型從文檔中查詢數(shù)據(jù)危尿。

例如:
集合inventory中存儲了一條name值為undefined的數(shù)據(jù)呐萌,通過$type操作符找到這條數(shù)據(jù),具體操作如下圖:

inventory集合.png

db.inventory.find({name: { $type: 6 }})

若直接通過db.inventory.find({name: undefined})查找會報(bào)錯

3.2 ObjectId

ObjectIds are small, likely unique, fast to generate, and ordered. ObjectId values consist of 12 bytes, where the first four bytes are a timestamp that reflect the ObjectId’s creation. Specifically:

  • a 4-byte value representing the seconds since the Unix epoch,
  • a 5-byte random value, and
  • a 3-byte counter, starting with a random value.

通過上述生成規(guī)則谊娇,是可以保證ObjectId做到全局唯一的肺孤。在Mongodb中,存儲在集合中的每一個文檔都需要一個唯一的[_id]作為主鍵。如果你插入文檔時忽略了[_id]赠堵,Mongodb會自動幫你生成[ObjectId]小渊。

4. mongodb shell、GUI與load()

可以通過終端茫叭、GUI以及外部js文件的形式(load()方法)來執(zhí)行CRUD命令酬屉。

5. sql和Mongodb statement對照關(guān)系

有熟悉關(guān)系型數(shù)據(jù)庫的可以參考https://docs.mongodb.com/manual/faq/fundamentals/#does-mongodb-support-sql看看兩者之間的區(qū)別。

6. mongodb運(yùn)算符

6.1 查詢運(yùn)算符

6.1.1 比較運(yùn)算符

比較不同的BSON類型值杂靶,使用3.2節(jié)中所講的$type操作符

Name Description
$eq 等于
$gt 大于
$gte 大于等于
$in 匹配在數(shù)組中的任意值
$lt 小于
$lte 小于等于
$ne 不等于
$nin 匹配不在數(shù)組中的值
6.1.1.1 $eq
  1. 語法: { <field>: { $eq: <value> } }
  2. 可用于比較
  • 簡單類型值
  • 復(fù)雜類型值(document梆惯、Array)
  1. Example
    用于比較的集合為inventory,包含的documents如下:
  • 匹配簡單類型
    命令: db.inventory.find( { qty: { $eq: 20 } } )
    結(jié)果:
  • 匹配復(fù)雜類型(document)
    命令: db.inventory.find( { "item.name": { $eq: "ab" } } )
    結(jié)果:
  • 匹配數(shù)組
    命令:db.inventory.find( { tags: { $eq: "B" } } )
    結(jié)果:

Note:這條命令查詢所有documents中tags字段中包含"B"的結(jié)果吗垮,也會匹配tags字段值為"B"的結(jié)果垛吗。這條命令相當(dāng)于db.inventory.find( { tags: { $in: ["B"] } } )

命令:db.inventory.find( { tags: { $eq: [ "A", "B" ] } } )
結(jié)果:

Note:這條命令查詢所有documents中tags字段完全匹配[ "A", "B" ]的結(jié)果烁登,或者匹配tags字段的某一項(xiàng)值為[ "A", "B" ]的結(jié)果怯屉。這條命令相當(dāng)于db.inventory.find( { tags: { $in: [[ "A", "B" ]] } } )

6.1.1.2 $gt
  1. 語法:{field: {$gt: value} }
  2. Example
    命令:db.inventory.find( { qty: { $gt: 20 } } )
    結(jié)果:

$gte饵沧、$lt锨络、$lte$ne都是類型的狼牺,不累述羡儿。

6.1.1.3 $in
  1. 語法:{ field: { $in: [<value1>, <value2>, ... <valueN> ] } }
  2. Examples
  • 匹配普通值
    命令:db.inventory.find( { qty: { $in: [ 5, 15 ] } } )
    結(jié)果:

Note:這條命令查詢所有documents中qty字段的值為5或者15的結(jié)果。當(dāng)然也可以使$or操作符來完成這一操作是钥,這里選擇$in而不是$or是因?yàn)檫@是對同一個字段的值的匹配掠归。

  • 匹配數(shù)組
    命令:db.inventory.find( { tags: { $in: ["A", "B"] } } )
    結(jié)果:

Note:這條命令查詢所有documents中tags字段包含"A"或者 "B"元素的結(jié)果。

  • 匹配正則
    命令:db.inventory.find( { tags: { $in: [ /^be/, /^st/ ] } } )
    結(jié)果:

Note:這條命令查詢所有documents中tags字段包含以be或者st字母開頭的元素的結(jié)果悄泥。
$nin$in相反虏冻,不累述。

6.1.2 邏輯運(yùn)算符
Name Description
$and
$not
$nor 或非
$or
6.1.2.1 $and
  1. 語法:{ $and: [ { <expression1> }, { <expression2> } , ... , { <expressionN> } ] }
  2. Examples
  • 同一字段
    命令:db.inventory.find( { $and: [ { price: { $ne: 1.99 } }, { price: { $exists: true } } ] } )
    結(jié)果:查找price字段的值存在且不等于1.99的結(jié)果弹囚。等同于db.inventory.find( { price: { $ne: 1.99, $exists: true } } )的執(zhí)行結(jié)果厨相。
  • 同一操作符
    命令:db.inventory.find( { $and : [ { $or : [ { price : 0.99 }, { price : 1.99 } ] }, { $or : [ { sale : true }, { qty : { $lt : 20 } } ] } ] } )
    結(jié)果:查找price字段等于0.99或者1.99并且sale字段等于true或者qty字段值小于20的結(jié)果。

Note: $and操作符實(shí)現(xiàn)了短路操作鸥鹉,如果<expression1>執(zhí)行為false蛮穿,MongoDB將不會再去執(zhí)行剩下的expression。

6.1.2.2 $not
  1. 語法:{ field: { $not: { <operator-expression> } } }
  2. Examples
    命令: db.inventory.find( { price: { $not: { $gt: 1.99 } } } )
    結(jié)果: 查找price字段值小于等于1.99的結(jié)果或者price字段不存在的結(jié)果宋舷。注意與 db.inventory.find( { price: { $lte: 1.99 } } )的區(qū)別绪撵。
6.1.2.3 $nor
  1. 語法:{ $nor: [ { <expression1> }, { <expression2> }, ... { <expressionN> } ] }
  2. Examples
    命令:db.inventory.find( { $nor: [ { price: 1.99 }, { sale: true } ] } )
    結(jié)果: 匹配的結(jié)果有4種情況
  • price字段的值不等于1.99并且sale字段的值不等于true
  • price字段的值不等于1.99并且sale字段不存在
  • price字段的值不存在并且sale字段的值不等于true
  • price字段的值不存在并且sale字段不存在
6.1.2.4 $or
  1. 語法:{ $or: [ { <expression1> }, { <expression2> }, ... , { <expressionN> } ] }
    Note:
  • 用法與$and類似。
  • 如果$or操作在同一個字段上進(jìn)行多次祝蝠,則建議使用$in
6.1.3 元素查詢運(yùn)算符
Name Description
$exists 指定字段是否存在
$type 匹配類型
6.1.3.1 $exists
  1. 語法: { field: { $exists: <boolean> } }
  2. 這個得益于document的無模式,不然也不會存在這個操作符
6.1.3.2 $type
  1. 語法: { field: { $type: [ <BSON type1> , <BSON type2>, ... ] } }
  2. 前面已經(jīng)演示過绎狭。
6.1.4 評估運(yùn)算符
Name Description
$expr 允許在查詢語言中使用聚合表達(dá)式
$jsonSchema 匹配符合與給定的json綱要的文檔
$mod 取模
$regex 正則
$text 文本對帶有文本索引的字段內(nèi)容執(zhí)行文本搜索
$where 匹配滿足javascript表達(dá)式的文檔
6.1.4.1 $expr
  1. 語法: { $expr: { <expression> } }
6.1.4.2 $jsonSchema
  1. 語法: { $jsonSchema: <schema> }
6.1.4.3 $mod
  1. 語法:{ field: { $mod: [ divisor, remainder ] } }
  2. Examples
    命令: db.inventory.find( { qty: { $mod: [ 4, 0 ] } } )
    結(jié)果:
    qty字段能被4整除的結(jié)果

Note:

  • 給$mod操作符傳遞的數(shù)組元素少于兩個或者多于兩個都會報(bào)錯细溅。
6.1.4.4 $regex
  1. 語法:
  • { <field>: { $regex: /pattern/, $options: '<options>' } }
  • { <field>: { $regex: 'pattern', $options: '<options>' } }
  • { <field>: { $regex: /pattern/<options> } }
    $options
Option Description
i 忽略大小寫
m 匹配使用了錨,例如^(代表開頭)和$(代表結(jié)尾)儡嘶,以及匹配\n后的字符串
x 忽視所有空白字符
s 允許點(diǎn)字符(.)匹配所有的字符喇聊,包括換行符

Note:

  • 正則性能不是很高,模糊匹配只能是表掃描蹦狂。
  • 不能在$in操作符中使用$regex表達(dá)式誓篱。
  • 要使用xoption或者soption,必須要使用$options操作符凯楔。
6.1.4.5 $text
  1. 語法:
  • {
    $text:
    {
    $search: <string>,
    $language: <string>,
    $caseSensitive: <boolean>,
    $diacriticSensitive: <boolean>
    }
    }
  1. 用法:首先對一個字段進(jìn)行全文索引窜骄,再進(jìn)行搜索。
  2. 全文檢索【全文索引】
  3. 中文分詞很麻煩摆屯,所以不支持中文
  4. 用處不是很大
6.1.4.6 $where

1.非常強(qiáng)大的遍歷器邻遏,通過包含js表達(dá)式的字符串或者function的模式,一排一排地去找最后的數(shù)據(jù)虐骑。

  1. 靈活但性能較差的模式【不走索引准验,全表掃描】
  2. 函數(shù)中的this指向當(dāng)前的迭代文檔
  3. Examples
    假設(shè)users文檔的結(jié)構(gòu)為:

找出與指定MD5 hash相同的name值所在文檔。
命令:db.foo.find( { $where: function() { return (hex_md5(this.name) == "9b53e667f30cd329dca1ec9e6a83e994") } } );
結(jié)果:

6.1.5 數(shù)組操作運(yùn)算符
Name Description
$all 匹配所有
$elemMatch 匹配內(nèi)嵌文檔或數(shù)組中的部分field
$size 匹配數(shù)組長度為指定大小的文檔
6.1.5.1 $all
  1. 語法: { <field>: { $all: [ <value1> , <value2> ... ] } }
  2. in類似廷没,但不同的是糊饱,in 只需滿足某一個值即可,而$all 必須滿足[ ]內(nèi)的所有值颠黎。
  3. Examples:
  • 命令: db.inventory.find({ tags: { $all: ['A', 'B' ] }})
  • 結(jié)果:
  • 命令:db.inventory.find({ tags: { $in: ['A', 'B' ] }})
  • 結(jié)果:
6.1.5.2 $elemMatch
  1. 語法:{ <field>: { $elemMatch: { <query1>, <query2>, ... } } }
  2. 不能在$elemMatch中使用$where或者$text
  3. Examples:
    假設(shè)scores集合的結(jié)構(gòu)為:
  • 命令:db.scores.find( { results: { $elemMatch: { $gte: 80, $lt: 85 } } } )
  • 結(jié)果:
6.1.5.3 $size
  1. Examples:
  • 命令:db.inventory.find( { tags: { $size: 1 } )
  • 結(jié)果:

6.2 update運(yùn)算符

6.2.1 字段update運(yùn)算符
Name Description
$currentDate 設(shè)置指定字段為當(dāng)前時間
$inc 將文檔中的某個field對應(yīng)的value自增/減某個數(shù)字
$min 將文檔中的某字段與指定值作比較另锋,如果原值小于指定值,則不更新盏缤;若大于指定值砰蠢,則更新
$max 與$min功能相反
$mul 將文檔中的某個field對于的value做乘法操作
$rename 重命名文檔中的指定字段的名
$set 更新文檔中的某一個字段,而不是全部替換
$setOnInsert 配合upsert操作唉铜,在作為insert時可以為新文檔擴(kuò)展更多的field
$unset 刪除文檔中的指定字段台舱,若字段不存在則不操作
6.2.1.1 $currentDate
  1. 語法:{ $currentDate: { <field1>: <typeSpecification1>, ... } }
  2. Examples:
  • 命令: db.users.update( { _id: 1 }, { $currentDate: { lastModified: true, "cancellation.date": { $type: "timestamp" } }, } )
6.2.1.2 $inc
  1. 語法:{ $inc: { <field1>: <amount1>, <field2>: <amount2>, ... } }
  2. Examples:
    假設(shè)products集合的結(jié)構(gòu)為
{
  _id: 1,
  sku: "abc123",
  quantity: 10,
  metrics: {
    orders: 2,
    ratings: 3.5
  }
}
  • 命令:db.products.update( { sku: "abc123" }, { $inc: { quantity: -2, "metrics.orders": 1 } } )
  • 結(jié)果:
6.2.1.3
  1. $min語法:{ $min: { <field1>: <value1>, ... } }
  2. $max語法:{ $max: { <field1>: <value1>, ... } }
  3. Examples:
  • 命令:db.tags.update( { _id: 1 }, { $min: { dateEntered: new Date("2013-09-25") } } )
  • 結(jié)果:更新_id為1所在文檔的dateEntered字段值,若原來dateEntered值對應(yīng)日期早于2013-09-25潭流,則不更新竞惋。
  • 命令:db.scores.update( { _id: 1 }, { $max: { highScore: 950 } } )
  • 結(jié)果:更新_id為1所在文檔的highScore字段值,若原來highScore值大于950灰嫉,則不更新拆宛。
6.2.1.4 $mul
  1. 語法:{ $mul: { <field1>: <number1>, ... } }
  2. Examples:
  • 命令: db.products.update( { _id: 1 }, { $mul: { qty: 2 } } )
  • 結(jié)果: 將qty字段值乘以2
6.2.1.5 $rename
  1. 語法:{$rename: { <field1>: <newName1>, <field2>: <newName2>, ... } }
6.2.1.6 $set
  1. 語法:{ $set: { <field1>: <value1>, ... } }
  2. Example:
  • 命令:db.products.update( { _id: 100 }, { $set: { "details.make": "zzz" } } )
6.2.1.7 $setOnInsert
  1. 語法:db.collection.update( <query>, { $setOnInsert: { <field1>: <value1>, ... } }, { upsert: true } )
  2. Examples:
    假設(shè)products集合的結(jié)構(gòu)為
{
  _id: 1,
  sku: "abc123",
  quantity: 10,
  metrics: {
    orders: 2,
    ratings: 3.5
  }
}
  • 命令:db.products.update({_id: 1}, {$set: {sku: 'test'}, $setOnInsert: {defaultQuantity: 100}}, {upsert: true})
  • 結(jié)果:
    沒有插入新的字段
  • 命令:db.products.update({_id: 2}, {$set: {sku: 'abc234'}, $setOnInsert: {defaultQuantity: 100}}, {upsert: true})
  • 結(jié)果:

    Note:
    當(dāng) [upsert: true]時,更新操作如果導(dǎo)致插入了一個新文檔讼撒,那么[$setOnInsert]將會為新文檔新文檔擴(kuò)展field浑厚,否則股耽,[$setOnInsert]不起任何作用。
6.2.1.8 $unset
  1. 語法:{ $unset: { <field1>: "", ... } }
  2. Examples:
  • 命令:db.products.update( { sku: "unknown" }, { $unset: { quantity: "", instock: "" } } )
  • 結(jié)果:刪除sku字段值為 "unknown"的文檔的quantityinstock兩個字段钳幅。
6.2.2 數(shù)組update運(yùn)算符
6.2.3 位運(yùn)算符

7. mongodb之CURD眾方法

7.1 Insert

方法 描述
db.collection.insertOne() 插入一條文檔
db.collection.insertMany() 插入多條文檔
db.collection.insert() 插入一條或多條文檔

7.2 Delete

方法 描述
db.collection.deleteOne() 刪除符合條件的一條文檔
db.collection.deleteMany() 刪除符合條件的多條文檔
db.collection.remove() 刪除符合條件的一條或多條文檔

7.3 Update

方法 描述
db.collection.update() 根據(jù)過濾條件更新文檔
db.collection.updateOne() 根據(jù)過濾條件更新一條文檔
db.collection.updateMany() 根據(jù)過濾條件更新多條文檔
db.collection.replaceOne() 根據(jù)過濾條件替換文檔

7.4 Query

方法 描述
db.collection.find() 查找文檔
db.collection.findAndModify() 查找并修改
db.collection.findOne() 查找第一個符合條件的文檔
db.collection.findOneAndDelete() 根據(jù)條件查找并刪除某條文檔
db.collection.findOneAndReplace() 根據(jù)條件查找并修改替換某條文檔
db.collection.findOneAndUpdate() 根據(jù)條件查找并更新某條文檔

8. mongodb索引

8.1. 索引的目的

加速查找

Indexes support the efficient execution of queries in MongoDB. Without indexes, MongoDB must perform a collection scan, i.e. scan every document in a collection, to select those documents that match the query statement. If an appropriate index exists for a query, MongoDB can use the index to limit the number of documents it must inspect.

8.2. 索引的實(shí)現(xiàn)原理

Btree

8.3 mondodb中支持的索引類型:

  • 單建索引(Single Field)
  • 復(fù)合索引(Compound Index)一定要主要鍵值的順序物蝙,字段在前在后有嚴(yán)格的區(qū)別
  • 多鍵值索引(Multikey Index)在array字段上建立一個索引,mongodb會將array的每一項(xiàng)進(jìn)行索引
  • 地理位置索引(Geospatial Index)
  • 全文索引(Text Indexes)不支持中文
  • Hash索引(Hashed Indexes)hash是為了做定值查找的

only support equality matches and cannot support range-based queries

  • 唯一索引(Unique Indexes)
  • 部分索引(Partial Indexes)使用更少的存儲空間敢艰,減少性能開銷
  • 稀疏索引(Sparse Indexes)
  • 過期索引(TTL Indexes)可以用來做定時過期的效果

8.4 如何創(chuàng)建索引

db.collection.createIndex(keys, options)

8.5 Examples
  1. 集合
    persons.js
var persons = [];

for (var i = 0; i < 10000; i++) {
  var name = `Graceji${i}`;
  var age = parseInt(Math.random() * 150) + 1;
  persons.push({ name, age });
}

db.person.insertMany(persons);

通過load('/Users/jina194/Desktop/persons.js')方法在person集合中插入了10000條數(shù)據(jù)诬乞。

  1. 創(chuàng)建單鍵索引: db.person.createIndex({ age: 1 })
  • 執(zhí)行db.person.find({ age: 88 }).explain()查看執(zhí)行效果:
{
    "queryPlanner" : {
        "plannerVersion" : 1,
        "namespace" : "test.person",
        "indexFilterSet" : false,
        "parsedQuery" : {
            "age" : {
                "$eq" : 88
            }
        },
         // winningPlan: 若干個計(jì)劃中挑選一個作為勝出者
        "winningPlan" : {
            "stage" : "FETCH",
            "inputStage" : {
                "stage" : "IXSCAN",  // IXSCAN代表索引
                "keyPattern" : {
                    "age" : 1
                },
                "indexName" : "age_1",
                "isMultiKey" : false,
                "multiKeyPaths" : {
                    "age" : [ ]
                },
                "isUnique" : false,
                "isSparse" : false,
                "isPartial" : false,
                "indexVersion" : 2,
                "direction" : "forward",
                "indexBounds" : {
                    "age" : [
                        "[88.0, 88.0]"
                    ]
                }
            }
        },
        "rejectedPlans" : [ ]
    },
    "serverInfo" : {
        "host" : "C02T8D1GGY25.local",
        "port" : 27017,
        "version" : "3.4.6",
        "gitVersion" : "c55eb86ef46ee7aede3b1e2a5d184a7df4bfb5b5"
    },
    "ok" : 1
}
  • 執(zhí)行db.person.find({ name: 'Graceji888' }).explain()查看執(zhí)行效果:
{
    "queryPlanner" : {
        ...
        "parsedQuery" : {
            "name" : {
                "$eq" : "Graceji888"
            }
        },
        "winningPlan" : {
            // 沒有建立索引的name字段采用的執(zhí)行計(jì)劃則為行掃描(COLLSCAN)
            "stage" : "COLLSCAN", 
            "filter" : {
                "name" : {
                    "$eq" : "Graceji888"
                }
            },
            "direction" : "forward"
        },
        ...
    },
        ...
}
  1. 創(chuàng)建復(fù)合索引:db.person.createIndex({ name: 1, age: 1 })
// db.person.getIndexes()
[
    {
        "v" : 2,
        "key" : {
            "_id" : 1
        },
        "name" : "_id_",
        "ns" : "test.person"
    },
    {
        "v" : 2,
        "key" : {
            "age" : 1
        },
        "name" : "age_1",
        "ns" : "test.person"
    },
    {
        "v" : 2,
        "key" : {
            "name" : 1,
            "age" : 1
        },
        "name" : "name_1_age_1",
        "ns" : "test.person"
    }
]
  • 執(zhí)行db.person.find({ name: 'Graceji888', age: 88 }).explain('executionStats')查看執(zhí)行效果:
{
    "queryPlanner" : {
        "plannerVersion" : 1,
        "namespace" : "test.person",
        "indexFilterSet" : false,
        "parsedQuery" : {
            "$and" : [
                {
                    "age" : {
                        "$eq" : 88
                    }
                },
                {
                    "name" : {
                        "$eq" : "Graceji888"
                    }
                }
            ]
        },
        "winningPlan" : {
            "stage" : "FETCH",
            "inputStage" : {
                "stage" : "IXSCAN", // 索引掃描
                "keyPattern" : {
                    "name" : 1,
                    "age" : 1
                },
                "indexName" : "name_1_age_1", // 執(zhí)行計(jì)劃所用索引的名字
                "isMultiKey" : false,
                "multiKeyPaths" : {
                    "name" : [ ],
                    "age" : [ ]
                },
                "isUnique" : false,
                "isSparse" : false,
                "isPartial" : false,
                "indexVersion" : 2,
                "direction" : "forward",
                "indexBounds" : {
                    "name" : [
                        "[\"Graceji888\", \"Graceji888\"]"
                    ],
                    "age" : [
                        "[88.0, 88.0]"
                    ]
                }
            }
        },
        // 拒絕的執(zhí)行計(jì)劃
        "rejectedPlans" : [
            {
                "stage" : "FETCH",
                "filter" : {
                    "name" : {
                        "$eq" : "Graceji888"
                    }
                },
                "inputStage" : {
                    "stage" : "IXSCAN",
                    "keyPattern" : {
                        "age" : 1
                    },
                    "indexName" : "age_1",
                    "isMultiKey" : false,
                    "multiKeyPaths" : {
                        "age" : [ ]
                    },
                    "isUnique" : false,
                    "isSparse" : false,
                    "isPartial" : false,
                    "indexVersion" : 2,
                    "direction" : "forward",
                    "indexBounds" : {
                        "age" : [
                            "[88.0, 88.0]"
                        ]
                    }
                }
            }
        ]
    },
    // 執(zhí)行狀態(tài)
    "executionStats" : {
        "executionSuccess" : true,
        "nReturned" : 0,
        "executionTimeMillis" : 1,
        "totalKeysExamined" : 0,
        "totalDocsExamined" : 0,
        "executionStages" : {
            "stage" : "FETCH",
            "nReturned" : 0, // 返回結(jié)果數(shù)
            "executionTimeMillisEstimate" : 0, // 執(zhí)行時間
            "works" : 2,
            "advanced" : 0,
            "needTime" : 0,
            "needYield" : 0,
            "saveState" : 0,
            "restoreState" : 0,
            "isEOF" : 1,
            "invalidates" : 0,
            "docsExamined" : 0, // 掃描條數(shù)
            "alreadyHasObj" : 0,
            "inputStage" : {
                "stage" : "IXSCAN",
                "nReturned" : 0,
                "executionTimeMillisEstimate" : 0,
                "works" : 1,
                "advanced" : 0,
                "needTime" : 0,
                "needYield" : 0,
                "saveState" : 0,
                "restoreState" : 0,
                "isEOF" : 1,
                "invalidates" : 0,
                "keyPattern" : {
                    "name" : 1,
                    "age" : 1
                },
                "indexName" : "name_1_age_1",
                "isMultiKey" : false,
                "multiKeyPaths" : {
                    "name" : [ ],
                    "age" : [ ]
                },
                "isUnique" : false,
                "isSparse" : false,
                "isPartial" : false,
                "indexVersion" : 2,
                "direction" : "forward",
                "indexBounds" : {
                    "name" : [
                        "[\"Graceji888\", \"Graceji888\"]"
                    ],
                    "age" : [
                        "[88.0, 88.0]"
                    ]
                },
                "keysExamined" : 0,
                "seeks" : 1,
                "dupsTested" : 0,
                "dupsDropped" : 0,
                "seenInvalidated" : 0
            }
        }
    },
    ...
}
  1. 創(chuàng)建多鍵值索引db.coll.createIndex( { <field>: < 1 or -1 > } ),與創(chuàng)建單鍵索引方式類似钠导。
  2. 創(chuàng)建Hash索引:將hashed作為索引key的value值震嫉,例如
    db.collection.createIndex( { _id: "hashed" } )
  3. 創(chuàng)建部分索引:
    通過db.collection.createIndex()方法和partialFilterExpression option創(chuàng)建。partialFilterExpression option接受的過濾條件有:
  • 相等性表達(dá)式 (例如:field: value 或者使用 $eq操作符)
  • $exists: true 表達(dá)式
  • $gt, $gte, $lt, $lte 表達(dá)式
  • $type 表達(dá)式
  • $and 操作符
    例如:
db.restaurants.createIndex(
   { cuisine: 1, name: 1 },
   { partialFilterExpression: { rating: { $gt: 5 } } }
)

以上例子創(chuàng)建了一個只針對rating字段值大于5的文檔的復(fù)合索引牡属∑倍拢可以在所有mongodb支持的索引類型中指定partialFilterExpression option。

  1. 創(chuàng)建稀疏索引
    假設(shè)有多個document一點(diǎn)關(guān)系也沒有(即結(jié)構(gòu)不一致湃望,除了主鍵_id是一樣的)换衬,這樣的documents怎么建立索引?——稀疏索引
    稀疏索引:建立的index字段必須在有這個字段的document上才可以建立证芭。

Sparse indexes only contain entries for documents that have the indexed field, even if the index field contains a null value. The index skips over any document that is missing the indexed field. The index is “sparse” because it does not include all documents of a collection. By contrast, non-sparse indexes contain all documents in a collection, storing null values for those documents that do not contain the indexed field.

通過db.collection.createIndex()方法和設(shè)置 sparse option為true來創(chuàng)建稀疏索引瞳浦,例如:db.addresses.createIndex( { "xmpp_id": 1 }, { sparse: true } ),在 addresses 集合的xmpp_id字段上創(chuàng)建了稀疏索引废士。

  1. 過期索引

TTL indexes are special single-field indexes that MongoDB can use to automatically remove documents from a collection after a certain amount of time or at a specific clock time.

通過db.collection.createIndex()方法和expireAfterSecondsoption(值的類型為date或者包含date的數(shù)組)來創(chuàng)建叫潦。例如:db.eventlog.createIndex( { "lastModifiedDate": 1 }, { expireAfterSeconds: 3600 } )在eventlog集合的lastModifiedDate字段上創(chuàng)建了一個TTL索引。
Note:

  • TTL 索引會讓文檔在指定的時間后過期官硝,即過期時間為被索引的字段值所代表的時間加上指定的過期秒數(shù)
  • 如果被索引的字段為包含多個時間值的數(shù)組矗蕊,mongodb會使用最早的日期來計(jì)算過期時間
  • 若被索引的字段不是date,或者包含date的數(shù)組氢架,則文檔不會過期
  • 如果某文檔不包括被索引的字段傻咖,則該文檔不會過期
  • 由于 TTL thread任務(wù)每60s執(zhí)行一次,所以TTL索引不能保證過期的數(shù)據(jù)能夠被立即刪除岖研,會存在一定的延時
  • TTL索引為單鍵索引卿操,復(fù)合索引是不支持的,會自動忽略expireAfterSeconds option
  • _id字段不支持TTL索引
  • 不能在capped collection中創(chuàng)建TTL索引
  • 如果一個字段已經(jīng)存在非TTL的單鍵索引孙援,就不能在同一個字段上再創(chuàng)建TTL索引
8.6 索引管理
  • 創(chuàng)建索引
    db.collection.createIndex()
  • 查看索引
    db.collection.getIndexes()
  • 移除索引
    db.collection.dropIndex()
    db.collection.dropIndexes()
  • 修改索引
    先刪除再創(chuàng)建
8.7 索引

9. MongoDB聚合管道(Aggregation Pipeline)

db.collection.aggregate()是基于數(shù)據(jù)處理的聚合管道害淤,每個文檔通過一個由多個階段(stage)組成的管道,可以對每個階段的管道進(jìn)行分組拓售、過濾等功能窥摄,然后經(jīng)過一系列的處理,輸出相應(yīng)的結(jié)果础淤。

Aggregate處理過程

9.1 db.collection.aggregate(pipeline, options)

  1. pipeline為array類型
  2. 因?yàn)?group和$sort都有內(nèi)存100M的限制崭放,所以將allowDiskUse開啟為true的話哨苛,就沒有此限制了,db.collection.aggregate(pipeline, { allowDiskUse: true })

9.2 Aggregation Pipeline Stages(有點(diǎn)多莹菱,只介紹常用的)

9.2.1 db.collection.aggregate() Stages

db.collection.aggregate( [ { <stage> }, ... ] )

Stage 描述
$group 將文檔依據(jù)指定字段的不同值進(jìn)行分組
$indexStats 查詢過程中的索引情況
$limit 限制返回的文檔個數(shù)
$lookup 與同一數(shù)據(jù)庫中其它集合之間進(jìn)行join操作
$match 根據(jù)query條件篩選文檔
$out 將最后計(jì)算結(jié)果寫入到指定的collection中
$project 對輸入文檔進(jìn)行添加新字段或刪除現(xiàn)有的字段移国,可以自定義字段的顯示狀態(tài)
$redact 字段所處的document結(jié)構(gòu)的級別
$sample 從待操作的集合中隨機(jī)返回指定數(shù)量的文檔
$skip 從待操作集合開始的位置跳過文檔的數(shù)目
$sort 對文檔按照指定字段排序
$unwind 將數(shù)組分解為單個的元素吱瘩,并與文檔的其余部分一同返回(Note:1.如果$unwind目標(biāo)字段不存在或者數(shù)組為空道伟,則整個文檔都會被忽略過濾掉 2.如果$unwind目標(biāo)字段不是一個數(shù)組,則會報(bào)錯)

9.2.1.1 $group

  1. 語法:{ $group: { _id: <expression>, <field1>: { <accumulator1> : <expression1> }, ... } }
  2. 可以分組的數(shù)據(jù)執(zhí)行如下表達(dá)式計(jì)算:
Name Description
$avg 計(jì)算平均值
$first 返回每組第一個文檔使碾,如果有排序蜜徽,按照排序,如果沒有按照默認(rèn)的存儲的順序的第一個文檔
$last 返回每組最后一個文檔票摇,如果有排序拘鞋,按照排序,如果沒有按照默認(rèn)的存儲的順序的最后一個文檔
$max 根據(jù)分組矢门,獲取集合中所有文檔對應(yīng)值的最大值
$min 根據(jù)分組盆色,獲取集合中所有文檔對應(yīng)值的最小值
$push 將指定的表達(dá)式的值添加到一個數(shù)組中
$addToSet 將表達(dá)式的值添加到一個集合中(無重復(fù)值)
$stdDevPop 計(jì)算總體標(biāo)準(zhǔn)差
$stdDevSamp 計(jì)算累積樣本標(biāo)準(zhǔn)差
$sum 計(jì)算總和
  • _id字段是必須的,其值是要進(jìn)行分組的key

9.2.1.2 $limit

  1. 語法:{ $limit: <positive integer> }

9.2.1.3 $lookup

  1. 語法
  • 相等性匹配
{
  $lookup:  
    {  
        from: <參與join的輔表>,  
        localField: <參與join匹配的本表字段>  
        foreignField: <參與join的輔表字段>  
        as: <將輔表數(shù)據(jù)輸出到此字段中>  
    }  
}
  • 多個Join條件和不相關(guān)子查詢
{
  $lookup:
     {
       from: <collection to join>,
       let: { <var_1>: <expression>, …, <var_n>: <expression> },
       pipeline: [ <pipeline to execute on the collection to join> ],
       as: <output array field>
     }
}

9.2.1.4 $match

  1. 語法:{ $match: { <query> } }
    Note:
  • 根據(jù)query條件篩選文檔祟剔,符合條件的文檔將會傳遞給下一個stage;
  • $match的語法和mongodb query語法一樣,且$match中不能使用aggregate expression或者比較操作悍汛,只能使用query操作允許的操作符;
  • $match用于篩選數(shù)據(jù)费尽,為了提高后續(xù)的數(shù)據(jù)處理效率,盡可能的將$match放于pipeline的前端以減少數(shù)據(jù)讀取或者計(jì)算量叛薯,加快聚合速度浑吟;
  • $match可以放在$out之前用于控制數(shù)據(jù)輸出量;
  • 如果$match放于pipeline的開始耗溜,還可以使用到索引機(jī)制提高數(shù)據(jù)查詢的性能组力。
    Examples:

    假設(shè)orders集合的結(jié)構(gòu)為:
    orders集合

inventory集合的結(jié)構(gòu)為:
inventory集合

9.2.1.5 $out

  1. 語法:{ $out: "<output-collection>" }
  2. 必須為pipeline最后一個階段管道,因?yàn)槭菍⒆詈笥?jì)算結(jié)果寫入到指定的collection中
  3. 如果不用$out抖拴,那么pipeline計(jì)算的結(jié)果并沒有序列化到硬盤燎字。

9.2.1.6 $project

  1. 語法:{ $project: { <specification(s)> } }

9.2.1.7 $sort

  1. 語法:{ $sort: { <field1>: <sort order>, <field2>: <sort order> ... } }
  2. <sort order>只能是以下值之一:
  • 1:升序
  • -1:降序
  • { $meta: "textScore" }:根據(jù) textScore metadata 降序排列
  1. 注意事項(xiàng):
  • 如果將$sort放到管道前面的話可以利用索引提高效率
  • 在管道中如果$sort出現(xiàn)在$limit之前的話,$sort只會對前$limit個文檔進(jìn)行操作城舞,這樣在內(nèi)存中也只會保留前$limit個文檔轩触,從而可以極大的節(jié)省內(nèi)存
  • $sort操作符默認(rèn)在內(nèi)存中進(jìn)行,超過此限制會報(bào)錯家夺,若要允許處理大型數(shù)據(jù)集脱柱,allowDiskUse 要設(shè)置為true
  1. Examples:
  • 命令1:
db.orders.aggregate([
   {
     $lookup:
       {
         from: "inventory",
         localField: "item",
         foreignField: "sku",
         as: "inventory_docs"
       }
  }
])
  • 結(jié)果1:通過orders集合中的item字段與inventory集合中的sku字段將兩個集合中的文檔進(jìn)行合并。
{
   "_id" : 1,
   "item" : "almonds",
   "price" : 12,
   "quantity" : 2,
   "inventory_docs" : [
      { "_id" : 1, "sku" : "almonds", "description" : "product 1", "instock" : 120 }
   ]
}
{
   "_id" : 2,
   "item" : "pecans",
   "price" : 20,
   "quantity" : 1,
   "inventory_docs" : [
      { "_id" : 4, "sku" : "pecans", "description" : "product 4", "instock" : 70 }
   ]
}
{
   "_id" : 3,
   "inventory_docs" : [
      { "_id" : 5, "sku" : null, "description" : "Incomplete" },
      { "_id" : 6 }
   ]
}
  • 命令2:
db.orders.aggregate([
   {
     $lookup:
       {
         from: "inventory",
         localField: "item",
         foreignField: "sku",
         as: "inventory_docs"
       }
  }拉馋,
  {
    $match: {
        price: { $gt: 0 }
    }
  }榨为,
])
  • 結(jié)果2:加入$match stage惨好,篩選出price字段大于0的結(jié)果。
{
   "_id" : 1,
   "item" : "almonds",
   "price" : 12,
   "quantity" : 2,
   "inventory_docs" : [
      { "_id" : 1, "sku" : "almonds", "description" : "product 1", "instock" : 120 }
   ]
}
{
   "_id" : 2,
   "item" : "pecans",
   "price" : 20,
   "quantity" : 1,
   "inventory_docs" : [
      { "_id" : 4, "sku" : "pecans", "description" : "product 4", "instock" : 70 }
   ]
}
  • 命令3:
db.orders.aggregate([
   {
     $lookup:
       {
         from: "inventory",
         localField: "item",
         foreignField: "sku",
         as: "inventory_docs"
       }
  }随闺,
  {
    $match: {
        price: { $gt: 0 }
    }
  }日川,
  {
     $group: {
         _id:  '$item',
        totalPrice: {$sum: {$multiply: ['$price', '$quantity']}},
        quantityAvg: {$avg: '$quantity'}
     }
  }
])
  • 結(jié)果3:加入$group stage,根據(jù)item字段分組矩乐,并計(jì)算totalPrice和QuantityAvg龄句。


  • 命令4:
db.orders.aggregate([
   {
     $lookup:
       {
         from: "inventory",
         localField: "item",
         foreignField: "sku",
         as: "inventory_docs"
       }
  },
  {
    $match: {
        price: { $gt: 0 }
    }
  }散罕,
  {
     $group: {
         _id:  '$item',
        totalPrice: {$sum: {$multiply: ['$price', '$quantity']}},
        quantityAvg: {$avg: '$quantity'}
     }
  },
  {
    $project: {
      _id: 1,
      totalPrice: 1,
      demo: 'my demo',
    }
  }
])
  • 結(jié)果4:加入$project stage分歇,去掉quantityAvg字段,并添加一個demo字段欧漱。
  • 命令5:
db.orders.aggregate([
   {
     $lookup:
       {
         from: "inventory",
         localField: "item",
         foreignField: "sku",
         as: "inventory_docs"
       }
  }职抡,
  {
    $match: {
        price: { $gt: 0 }
    }
  },
  {
     $group: {
         _id:  '$item',
        totalPrice: {$sum: {$multiply: ['$price', '$quantity']}},
        quantityAvg: {$avg: '$quantity'}
     }
  },
  {
    $project: {
      _id: 1,
      totalPrice: 1,
      demo: 'my demo',
    }
  },
  {
     $sort: { totalPrice: -1 }
  }
])
  • 結(jié)果5:加入$sort误甚,根據(jù)totalPrice字段降序排序缚甩。
  • 命令6:
db.orders.aggregate([
   {
     $lookup:
       {
         from: "inventory",
         localField: "item",
         foreignField: "sku",
         as: "inventory_docs"
       }
  },
  {
    $match: {
        price: { $gt: 0 }
    }
  }窑邦,
  {
     $group: {
         _id:  '$item',
        totalPrice: {$sum: {$multiply: ['$price', '$quantity']}},
        quantityAvg: {$avg: '$quantity'}
     }
  },
  {
    $project: {
      _id: 1,
      totalPrice: 1,
      demo: 'my demo',
    }
  },
  {
     $limit: 1
  }
])
  • 結(jié)果6:加入$limit擅威,限制返回的文檔的個數(shù)。
  • 命令6:
db.orders.aggregate([
   {
     $lookup:
       {
         from: "inventory",
         localField: "item",
         foreignField: "sku",
         as: "inventory_docs"
       }
  }奕翔,
  {
    $match: {
        price: { $gt: 0 }
    }
  }裕寨,
  {
     $group: {
         _id:  '$item',
        totalPrice: {$sum: {$multiply: ['$price', '$quantity']}},
        quantityAvg: {$avg: '$quantity'}
     }
  },
  {
    $project: {
      _id: 1,
      totalPrice: 1,
      demo: 'my demo',
    }
  },
  {
     $limit: 1
  },
   {
     $out: 'myaggretion'
   },
])
  • 結(jié)果7:加入$out,將結(jié)果寫入到myaggretion集合中派继。

9.2.2 db.aggregate() Stages

db.aggregate( [ { <stage> }, ... ] )

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末宾袜,一起剝皮案震驚了整個濱河市,隨后出現(xiàn)的幾起案子驾窟,更是在濱河造成了極大的恐慌庆猫,老刑警劉巖,帶你破解...
    沈念sama閱讀 211,290評論 6 491
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件绅络,死亡現(xiàn)場離奇詭異月培,居然都是意外死亡,警方通過查閱死者的電腦和手機(jī)恩急,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 90,107評論 2 385
  • 文/潘曉璐 我一進(jìn)店門杉畜,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人衷恭,你說我怎么就攤上這事此叠。” “怎么了随珠?”我有些...
    開封第一講書人閱讀 156,872評論 0 347
  • 文/不壞的土叔 我叫張陵灭袁,是天一觀的道長猬错。 經(jīng)常有香客問我,道長茸歧,這世上最難降的妖魔是什么倦炒? 我笑而不...
    開封第一講書人閱讀 56,415評論 1 283
  • 正文 為了忘掉前任,我火速辦了婚禮软瞎,結(jié)果婚禮上逢唤,老公的妹妹穿的比我還像新娘。我一直安慰自己铜涉,他們只是感情好智玻,可當(dāng)我...
    茶點(diǎn)故事閱讀 65,453評論 6 385
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著芙代,像睡著了一般。 火紅的嫁衣襯著肌膚如雪盖彭。 梳的紋絲不亂的頭發(fā)上纹烹,一...
    開封第一講書人閱讀 49,784評論 1 290
  • 那天,我揣著相機(jī)與錄音召边,去河邊找鬼铺呵。 笑死,一個胖子當(dāng)著我的面吹牛隧熙,可吹牛的內(nèi)容都是我干的片挂。 我是一名探鬼主播,決...
    沈念sama閱讀 38,927評論 3 406
  • 文/蒼蘭香墨 我猛地睜開眼贞盯,長吁一口氣:“原來是場噩夢啊……” “哼音念!你這毒婦竟也來了?” 一聲冷哼從身側(cè)響起躏敢,我...
    開封第一講書人閱讀 37,691評論 0 266
  • 序言:老撾萬榮一對情侶失蹤闷愤,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后件余,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體讥脐,經(jīng)...
    沈念sama閱讀 44,137評論 1 303
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 36,472評論 2 326
  • 正文 我和宋清朗相戀三年啼器,在試婚紗的時候發(fā)現(xiàn)自己被綠了旬渠。 大學(xué)時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點(diǎn)故事閱讀 38,622評論 1 340
  • 序言:一個原本活蹦亂跳的男人離奇死亡端壳,死狀恐怖告丢,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情更哄,我是刑警寧澤芋齿,帶...
    沈念sama閱讀 34,289評論 4 329
  • 正文 年R本政府宣布腥寇,位于F島的核電站,受9級特大地震影響觅捆,放射性物質(zhì)發(fā)生泄漏赦役。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 39,887評論 3 312
  • 文/蒙蒙 一栅炒、第九天 我趴在偏房一處隱蔽的房頂上張望掂摔。 院中可真熱鬧,春花似錦赢赊、人聲如沸乙漓。這莊子的主人今日做“春日...
    開封第一講書人閱讀 30,741評論 0 21
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽叭披。三九已至,卻和暖如春玩讳,著一層夾襖步出監(jiān)牢的瞬間涩蜘,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 31,977評論 1 265
  • 我被黑心中介騙來泰國打工熏纯, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留同诫,地道東北人。 一個月前我還...
    沈念sama閱讀 46,316評論 2 360
  • 正文 我出身青樓樟澜,卻偏偏與公主長得像误窖,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子秩贰,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 43,490評論 2 348

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

  • 關(guān)于Mongodb的全面總結(jié) MongoDB的內(nèi)部構(gòu)造《MongoDB The Definitive Guide》...
    中v中閱讀 31,905評論 2 89
  • MongoDB介紹 MongoDB是一個基于分布式文件存儲的開源文檔數(shù)據(jù)庫霹俺。由C++語言編寫。旨在為WEB應(yīng)用提供...
    小廚筆記閱讀 1,300評論 0 2
  • 這里是閱讀了《MongoDB權(quán)威指南》后做的相關(guān)筆記。 一蝗罗、文檔 文檔是MongoDB的核心概念艇棕。文檔就是鍵值對的...
    yjaal閱讀 629評論 0 4
  • 一 基本概念 MongoDB中數(shù)據(jù)的結(jié)構(gòu)為:庫、集合串塑、文檔 1 數(shù)據(jù)庫 多個集合可以組成數(shù)據(jù)庫沼琉。MongoDb的單...
    周東波_db閱讀 2,374評論 0 4
  • 簡介 MongoDB 是非關(guān)系型數(shù)據(jù)庫。支持的數(shù)據(jù)結(jié)構(gòu)非常松散桩匪,是類似 json 的 bson 格式打瘪,因此可以存儲...
    與蟒唯舞閱讀 306評論 0 1