MongoDB權威指南—讀書筆記(Part one)

MongoDB權威指南—讀書筆記(Part One)

非常棒的官方網站!

MongoDB基礎知識

  • 文檔是MongoDB中數據的基本單元,非常類似於關係學數據庫管理系統中的行,但更具表現力。
  • 類似地,集合(collection)可以看作是一個擁有動態模式(dynamic schema)的表。
  • MongoDB的一個實例可以擁有多個相互獨立的數據庫(database),每一個數據庫都擁有自己的集合。
  • 每一個文檔都由一個特殊的鍵”_id”,這個鍵在文檔所屬的集合中是唯一的。
  • MongoDB自帶了一個簡單但功能強大的JavaScript shell,可用於管理MongoDB的實例或數據操作。

文檔

文檔就是鍵值對的一個有序集。
例如:{"greeting":"Hello,world!","foo":3}
文檔中的值可以是多種不同的數據類型(甚至可以是一個完整的內嵌文檔)。

文檔的鍵是字符串。除了少數例外情況,鍵可以使用任意UTF-8字符。
- 鍵不能含有\0(空字符)。這個字符用於表示鍵的結尾。
- .和$具有特殊意義,只能在特定環境下使用。通常,這兩個字符是被保留的。

MongoDB不但區分類型,而且區分大小寫,例:

下面兩組均不相同:
{"foo":3}
{"foo":"3"}
/*-------------------------*/
{"foo":3}
{"Foo":3}

MongoDB的文檔不能有重複的鍵。

集合

集合就是一組文檔。

動態模式

集合是動態模式的。這意味着一個集合裏面的文檔可以是各式各樣的。

“既然沒有必要區分不同類型文檔的模式,爲什麼還要使用多個集合呢?”:
- 如果把各種各樣的文檔不加區分地放在同一個集合裏,無論對開發者還是對管理員來說都將是噩夢。
- 在一個集合裏查詢特定類型的文檔在速度上頁很不划算,分開查詢多個集合要快得多。
- 把同種類型的文檔放在一個集合裏,數據會更加集中。
- 創建索引時,需要使用文檔的附加結構(特別是創建唯一索引時)。索引是按照集合來定義的。在一個集合中只放入一種類型的文檔,可以更有效地對集合進行索引。

命名

集合名可以是滿足下列條件的任意UTF-8
- 集合名不能是空字符串(“”)。
- 集合名不能包含\0字符(空字符),這個字符表示集合名的結束。
- 集合名不能以”system.”開頭,這是爲系統集合保留的前綴。
- 用戶創建的集合不能在集合名中包含保留字符”$”。引文某些系統生成的集合中包含$,很多驅動程序確實支持在集合名裏包含該字符。除非你要訪問這種系統創建的集合,否則不應該在集合名中包含$

子集合

組織集合的一種慣例是使用”.”分隔不同命名空間的集合。

數據庫

在MongoDB中,多個文檔組成集合,而多個集合可以組成數據庫。一個MongoDB實例可以承載多個數據庫,每個數據庫擁有0個或者多個集合。每個數據庫都由獨立的權限,即便是在磁盤上,不同的數據庫也放置在不同的文件中。按照經驗,我們將有關一個應用程序的所有數據都存儲在同一個數據庫中。要想在同一個MongoDB服務器上存放多個應用程序或者用戶的數據,就需要使用不同的數據庫。

數據庫通過名稱來識別,數據庫名可以是滿足以下條件的任意UTF-8字符串:
- 不能是空字符串(“”)
- 不得含有/,\,.,",*,<,>,:,|,?,$ (一個空格),\0。基本上,只能使用ASCII中的字母和數字
- 數據庫名區分大小寫,即便是在不去放大小寫的文件系統中也是如此。簡單起見,數據庫名應全部小寫。
- 數據庫名最多爲64字節。

另外,有一些數據庫名是保留的,可以直接訪問這些有特殊語義的數據庫。這些數據庫如下所示。
- admin
從身份驗證的角度來說,這是”root”數據庫。如果將一個用戶添加到admin數據庫,這個用戶將自動獲得所有數據庫的權限。再者,一些特殊的服務器端命令也只能從admin數據庫運行,如列出所有數據庫或關閉服務器。

  • local
  • config

啓動MongoDB

MongoDB shell簡介

這是JavaScript shell

運行shell

mongo

MongoDB客戶端

啓動時,shell會連到MongoDB服務器的test數據庫,並將數據庫連接賦值給全局變量db。

如果想要查看db當前指向哪個數據庫,可以使用db命令:

> db
test

選擇數據庫:

> use foobar
switched to db foobar
shell中的基本操作
創建

insert函數可將一個文檔添加到集合中。例:

{
    "title" : "My Blog Post",
    "content" : "Here's my blog post.",
    "date" : ISODate("2014-11-11T10:39:47.464Z")
}
> db.blog.insert(post)
WriteResult({ "nInserted" : 1 })

//結果
> db.blog.find()
{ "_id" : ObjectId("5461e78066f397ec9d6bf459"), "title" : "My Blog Post", "content" : "Here's my blog post.", "date" : ISODate("2014-11-11T10:39:47.464Z") }
讀取

find和findOne方法可以用於查詢集合中的文檔。若只想查看一個文檔,可用findOne:

> db.blog.findOne()
{
    "_id" : ObjectId("5461e78066f397ec9d6bf459"),
    "title" : "My Blog Post",
    "content" : "Here's my blog post.",
    "date" : ISODate("2014-11-11T10:39:47.464Z")
}

find和findOne可以接受一個查詢文檔作爲限定條件。這樣就可以查詢滏陽河一定條件的文檔。使用find時,shell會自動顯示最多20個匹配的文檔,也可獲取更多文檔。

更新

使用update。update接受(至少)兩個參數:
1. 限定條件
2. 新的文檔

> db.blog.update({title:"My Blog Post"},post)
WriteResult({ "nMatched" : 1, "nUpserted" : 0, "nModified" : 1 })
> db.blog.find()
{ "_id" : ObjectId("5461e78066f397ec9d6bf459"), "title" : "My Blog Post", "content" : "Here's my blog post.", "date" : ISODate("2014-11-11T10:39:47.464Z"), "comments" : [ ] }
刪除

使用remove方法,如果需要全部刪除則使用remove(“”)

> db.blog.remove({title:"My Blog Post"})
WriteResult({ "nRemoved" : 1 })
> db.blog.find()
/*---------------*/
> db.blog.remove("")
WriteResult({ "nRemoved" : 1 })
> db.blog.find()

數據類型

基本數據類型
  • null
  • boolean
  • number
  • string
  • date
  • 數組
  • 內嵌文檔
  • 對象id
  • 二進制數據
  • 代碼
    查詢和文檔中可以包含任意JavaScript代碼
日期

使用new Date(...)

數組

這裏的數組可以包含不同的數據類型的元素
{"things":["pie",3.14]}

內嵌文檔
{
    "name":"John Doe",
    "address":{
        "city":"Anytown",
        "state":"NY"
    }
}
_id和ObjectId

是在一個集合中每個文檔的唯一標識。

生成方式看書p20

使用MongoDB shell

可以連接到其他機器的MongoDB上(服務器)

shell小貼士

help

使用shell執行腳本

mongo script1.js script2.js
mongo shell會依次執行傳入的腳本,然後退出。

也可以使用load()函數,從交互式shell中運行腳本:
load("script1.js")

在腳本中可以訪問db變量,以及其他全局變量。然而,shell輔助函數(比如”use db”和”show collections”)不可以在文件中使用。這些輔助函數都有對應的JavaScript函數。

創建.mongorc.js文件

在用戶主目錄下創建一個名爲.mongorc.js的文件,這是一個before文件,最常見的用途之一是移除那些比較”危險”的shell輔助函數。可以在這裏集中重寫這些方法。例如

var no = function() {
print("Not on my watch.");
};
// Prevent dropping databases
db.dropDatabase = DB.prototype.dropDatabase = no;
// Prevent dropping collections
DBCollection.prototype.drop = no;
// Prevent dropping indexes
DBCollection.prototype.dropIndex = no;

改變數據庫函數時,要確保同時對db變量和DB原型進行改變(如上例)。如果只改變了其中一個,那麼db變量可能沒有改變,或者這些改變在新使用的所有數據庫(運行use anotherDB命令)中都不會生效。

如果在啓動shell時指定–norc參數,就可以禁止加載.mongorc.js。

定製shell提示

將prompt變量設爲一個字符串或者函數,就可以重寫默認的shell提示。
例如,顯示當前使用的數據庫:

prompt = function() {
if (typeof db == 'undefined') {
return '(nodb)> ';
}
// Check the last db operation
try {
db.runCommand({getLastError:1});
}
catch (e) {
print(e);
}
return db+"> ";
};

可以在.mongorc.js中定製自己想要的提示。

編輯複合變量
集合命名注意事項

創建,更新和刪除文檔

插入並保存文檔

db.mycollection.insert({"key":"value"})
這個操作會給文檔自動增加一個”_id”鍵(要是原來沒有的話),然後將其保存到MongoDB中。

批量插入

暫時找不到2.6批量插入的方法。

插入效驗

文檔大小必須小於16MB

刪除文檔

db.mycollection.remove({})/remove{"key":"value"}

刪除速度

如果要清空整個集合,使用drop直接刪除集合會更快(然後在這個空集合上重建各項索引)。

更新文檔

文檔存入數據庫以後,就可以使用update方法來更新它。update有兩個參數:
1. 查詢文檔,用於定位需要更新的目標文檔
2. 修改器(modifier)文檔,用於說明要對找到的文檔進行哪些修改。

更新操作是不可分割的:若是兩個更新同時發生,先到達服務器的先執行,接着執行另外一個。

文檔替換

最簡單的更新就是用一個新文檔完全替換匹配的文檔。這適用於進行大規模模式遷移的情況。

> db.blog.find()
{ "_id" : ObjectId("54641eeaa5fa343b3e09e590"), "name" : "Bing" }
> var name = db.blog.findOne()
> name
{ "_id" : ObjectId("54641eeaa5fa343b3e09e590"), "name" : "Bing" }
> name.age=22
22
> db.blog.update({"name":"Bing"},name);
WriteResult({ "nMatched" : 1, "nUpserted" : 0, "nModified" : 1 })
> db.blog.find()
{ "_id" : ObjectId("54641eeaa5fa343b3e09e590"), "name" : "Bing", "age" : 22 }
> 

使用”_id”作爲查詢條件比使用隨機字段速度更快,因爲是通過”_id”建立的索引。

使用修改器

通常文檔只會有一部分要更新。可以使用原子性的更新修改器(update modifier),指定對文檔中的某些字段進行更新。

  • $inc 修改器增加某個鍵的值,若該鍵不存在則創建一個只能用於整型,長整型或雙精度浮點型的值
> db.blog.find()
{ "_id" : ObjectId("54641eeaa5fa343b3e09e590"), "name" : "Bing", "age" : 22 }
> db.blog.update({"name":"Bing"},
... {"$inc":{"age":-1}})
WriteResult({ "nMatched" : 1, "nUpserted" : 0, "nModified" : 1 })
> db.blog.find()
{ "_id" : ObjectId("54641eeaa5fa343b3e09e590"), "name" : "Bing", "age" : 21 }
  • $set 指定一個字段的值。如果這個字段不存在,則創建它。
> db.blog.find()
{ "_id" : ObjectId("54641eeaa5fa343b3e09e590"), "name" : "Bing", "age" : 21 }
> db.blog.update({"name":"Bing"},
... {"$set":{"favorite moive in the year":"星際穿越"}})
WriteResult({ "nMatched" : 1, "nUpserted" : 0, "nModified" : 1 })
> db.blog.find()
{ "_id" : ObjectId("54641eeaa5fa343b3e09e590"), "name" : "Bing", "age" : 21, "favorite moive in the year" : "星際穿越" }
  • $unset 將一個鍵完全刪除
> db.blog.find()
{ "_id" : ObjectId("54641eeaa5fa343b3e09e590"), "name" : "Bing", "age" : 21, "favorite moive in the year" : "星際穿越" }
> db.blog.update({"name":"Bing"},
... {"$unset":{"age":1}})
WriteResult({ "nMatched" : 1, "nUpserted" : 0, "nModified" : 1 })
> db.blog.find()
{ "_id" : ObjectId("54641eeaa5fa343b3e09e590"), "name" : "Bing", "favorite moive in the year" : "星際穿越" }
  • $push 向已有的數組末尾加入一個元素,要是沒有就創建一個新的數組。
> db.blog.find()
{ "_id" : ObjectId("54641eeaa5fa343b3e09e590"), "name" : "Bing", "favorite moive in the year" : "星際穿越" }
> db.blog.update({"name":"Bing"},
... {"$push":{"comments":{"content":"getArray"}}})
WriteResult({ "nMatched" : 1, "nUpserted" : 0, "nModified" : 1 })
> db.blog.find()
{ "_id" : ObjectId("54641eeaa5fa343b3e09e590"), "name" : "Bing", "favorite moive in the year" : "星際穿越", "comments" : [ { "content" : "getArray" } ] }
> db.blog.update({"name":"Bing"}, {"$push":{"comments":{"content":"2"}}})
WriteResult({ "nMatched" : 1, "nUpserted" : 0, "nModified" : 1 })
> db.blog.find()
{ "_id" : ObjectId("54641eeaa5fa343b3e09e590"), "name" : "Bing", "favorite moive in the year" : "星際穿越", "comments" : [ { "content" : "getArray" }, { "content" : "2" } ] }
  • $each $push的簡單使用形式,可以通過一次”$push”操作添加多個值
> db.blog.find()
{ "_id" : ObjectId("54641eeaa5fa343b3e09e590"), "name" : "Bing", "favorite moive in the year" : "星際穿越", "comments" : [ { "content" : "getArray" }, { "content" : "2" } ] }
> db.blog.update({"name":"Bing"},
... {"$push":{"hourly":{"$each":[563.321,532.0,234]}}})
WriteResult({ "nMatched" : 1, "nUpserted" : 0, "nModified" : 1 })
> db.blog.find()
{ "_id" : ObjectId("54641eeaa5fa343b3e09e590"), "name" : "Bing", "favorite moive in the year" : "星際穿越", "comments" : [ { "content" : "getArray" }, { "content" : "2" } ], "hourly" : [ 563.321, 532, 234 ] }

$slice 和$push組合使用,可以保證數組不會超出設定號的最大長度,這實際上就得到了一個最多包含N個元素的數組。$slice的值必須是負整數。不能智將#slice或者$sort與$push配合使用,且必須使用$each。

> db.blog.find()
{ "_id" : ObjectId("54641eeaa5fa343b3e09e590"), "name" : "Bing", "favorite moive in the year" : "星際穿越", "comments" : [ { "content" : "getArray" }, { "content" : "2" } ], "hourly" : [ 563.321, 532, 234, 32, 43, 53, 1, 2345, 322, 0.23, 324 ] }
> db.blog.update(
... {name:"Bing"},
... {
...     $push:{
...         hourly:{
...         $each:[3,4,5],
...         $slice:-5
...         }
...     }
... }
... )
WriteResult({ "nMatched" : 1, "nUpserted" : 0, "nModified" : 1 })
> db.blog.find()
{ "_id" : ObjectId("54641eeaa5fa343b3e09e590"), "name" : "Bing", "favorite moive in the year" : "星際穿越", "comments" : [ { "content" : "getArray" }, { "content" : "2" } ], "hourly" : [ 324, 22.2, 3, 4, 5 ] }
/*---------------------------------------------------------*/
> db.blog.update( {name:"Bing"},
...{ $push:{ hourly:{ $each:[3,4,5], $slice:-5,$sort:-1 } } } )
WriteResult({ "nMatched" : 1, "nUpserted" : 0, "nModified" : 1 })
> db.blog.find()
{ "_id" : ObjectId("54641eeaa5fa343b3e09e590"), "name" : "Bing", "favorite moive in the year" : "星際穿越", "comments" : [ { "content" : "getArray" }, { "content" : "2" } ], "hourly" : [ 5, 4, 4, 3, 3 ] }
  • $ne 保證數組內的元素不重複
> db.blog.find()
{ "_id" : ObjectId("54641eeaa5fa343b3e09e590"), "name" : "Bing", "favorite moive in the year" : "星際穿越", "comments" : [ { "content" : "getArray" }, { "content" : "2" } ], "hourly" : [ 5, 4, 4, 3, 3 ] }
> db.blog.update({name:{"$ne":"Bing"}}, 
...{$push:{name:"Bing"}})
WriteResult({ "nMatched" : 0, "nUpserted" : 0, "nModified" : 0 })
> db.blog.find()
{ "_id" : ObjectId("54641eeaa5fa343b3e09e590"), "name" : "Bing", "favorite moive in the year" : "星際穿越", "comments" : [ { "content" : "getArray" }, { "content" : "2" } ], "hourly" : [ 5, 4, 4, 3, 3 ] }
  • $addToSet 作用與$ne一致,但是它可以與$each組合使用
> db.blog.find()
{ "_id" : ObjectId("54641eeaa5fa343b3e09e590"), "name" : "Bing", "favorite moive in the year" : "星際穿越", "comments" : [ { "content" : "getArray" }, { "content" : "2" } ], "hourly" : [ 5, 4, 4, 3, 3 ] }
> db.blog.update({name:"Bing"},
... {$addToSet:{
... hourly:{
... $each:[3,53,0,66]}}})
WriteResult({ "nMatched" : 1, "nUpserted" : 0, "nModified" : 1 })
> db.blog.find()
{ "_id" : ObjectId("54641eeaa5fa343b3e09e590"), "name" : "Bing", "favorite moive in the year" : "星際穿越", "comments" : [ { "content" : "getArray" }, { "content" : "2" } ], "hourly" : [ 5, 4, 4, 3, 3, 53, 0, 66 ] }
  • $pull 刪除元素
    db.blog.update({name:"Bing"},{$pull:{comments:{}}})

  • $pop 刪除元素
    {$pop:{"key":1}}從數組末尾刪除,{$pop:{"key":-1}}從數組開頭刪除

  • 基於位置的數組修改器,數組下標都是以0開頭的,可以將下標直接作爲鍵來選擇元素

> db.blog.find()
{ "_id" : ObjectId("54641eeaa5fa343b3e09e590"), "name" : "Bing", "favorite moive in the year" : "星際穿越", "hourly" : [ 6, 4, 4, 3, 3, 53, 0, 66 ] }
> db.blog.update({name:"Bing"}, {$inc:{"hourly.0":1}})
WriteResult({ "nMatched" : 1, "nUpserted" : 0, "nModified" : 1 })
> db.blog.find()
{ "_id" : ObjectId("54641eeaa5fa343b3e09e590"), "name" : "Bing", "favorite moive in the year" : "星際穿越", "hourly" : [ 7, 4, 4, 3, 3, 53, 0, 66 ] }
upsert

upsert是一種特殊的更新。要是沒有找到符合更新條件的文檔,就會以這個條件和更新文檔爲基礎創建一個新的文檔。如果找到了匹配的文檔,則正常更新。當update第三個參數爲true時代表upsert。

更新多個文檔

update第四個參數爲true代表更新多個文檔。

返回被更新的文檔

通過findAndModify得到被更新的文檔

API
db.collection.findAndModify({
    query: <document>,
    sort: <document>,
    remove: <boolean>,
    update: <document>,
    new: <boolean>,
    fields: <document>,
    upsert: <boolean>
});
> db.blog.findAndModify({query: { name: "Bing" },update: { $inc: { score: 1 } },upsert: true})
{
    "_id" : ObjectId("54641eeaa5fa343b3e09e590"),
    "name" : "Bing",
    "favorite moive in the year" : "星際穿越",
    "hourly" : [
        7,
        4,
        4,
        3,
        3,
        53,
        0,
        66
    ]
}
> db.blog.find()
{ "_id" : ObjectId("54641eeaa5fa343b3e09e590"), "name" : "Bing", "favorite moive in the year" : "星際穿越", "hourly" : [ 7, 4, 4, 3, 3, 53, 0, 66 ], "score" : 1 }

寫入安全機制

寫入安全(Write Concern)是一種客戶端設置,用於控制寫入的安全級別。


查詢

find()間接

db.collection.find(<criteria>, <projection>)criteria相當於where語句,projection相當於投影
eg:

//criteria
db.bios.find()
db.bios.find( { _id: 5 } )
db.bios.find(
   {
      _id: { $in: [ 5,  ObjectId("507c35dd8fada716c89d0013") ] }
   }
)
db.collection.find( { field: { $gt: value1, $lt: value2 } } );
db.bios.find(
   {
      awards: {
                $elemMatch: {
                     award: "Turing Award",
                     year: { $gt: 1980 }
                }
      }
   }
)
//projection
db.products.find( { qty: { $gt: 25 } }, { item: 1, qty: 1 } )
db.bios.find( { }, { name: 1, contribs: 1 } )
db.products.find( { qty: { $gt: 25 } }, { _id: 0, qty: 0 } )
db.bios.find(
   { },
   {
     _id: 0,
     'name.last': 1,
     contribs: { $slice: 2 }
   }
)

現在介紹criteria有什麼:
- $lt : <
- $lte: <=
- $gt : >
- $gte: >=
- $in : 相當於sql中的in,可以用來查詢一個鍵的多個值
- $nin: 與in相反,not in
- $ne : !=
- $or : or
db.raffle.find({"$or" : [{"ticket_no" : 725}, {"winner" : true}]})
db.raffle.find({"$or" : [{"ticket_no" : {"$in" : [725, 542, 390]}},{"winner" : true}]})
- $and: and
db.users.find({"$and" : [{"x" : {"$lt" : 1}}, {"x" : 4}]})
匹配那些”x”字段的值小於1並且等於4的文檔。但不如以下效率高:
db.users.find({"x" : {"$lt" : 1, "$in" : [4]}})
- 可以查詢null
db.c.find({"y" : null})
- 可以查詢正則表達式
db.users.find({"name" : /joe/i})
i是忽略大小寫,/這裏面是正則表達式內容/
- $exists
db.inventory.find( { qty: { $exists: true, $nin: [ 5, 15 ] } } )
選擇不等於5和15的
- $all 通過多個元素來匹配數組

db.food.find({fruit : {$all : ["apple", "banana"]}})
{"_id" : 1, "fruit" : ["apple", "banana", "peach"]}
{"_id" : 3, "fruit" : ["cherry", "banana", "apple"]}
  • $size 查詢特定長度的數組
  • 更多

現在介紹projection有什麼:
最下面

where查詢

遊標

數據庫使用遊標返回find的執行結果。客戶端對遊標的實現通常能夠對最終結果進行有效的控制。可以限制結果的數量,略過部分結果,根據任意鍵按任意順序的組合對結果進行各種排序,或者是執行其他一些強大的操作。
創建一個遊標(先創建文檔吧):

> for(i=0; i<100; i++) {
...
db.collection.insert({x : i});
... }
> var cursor = db.collection.find();

這麼做的好擦是可以一次查看一條結果。

> while(cursor.hasNext()){
... obj=cursor.next();
... print(obj.x);
... }

cursor.hasNext()檢查是否有後續結果存在,然後用cursor.next()獲取它
它還實現了迭代器接口:

cursor.forEach(
    function(x){
    print(x.x)
})

更多

limit,skip,sort
db.test.find().limit(3)
db.test.find().skip(3)//略過
db.test.find().sort({x:-1})//1正序,-1倒序
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章