0 由于工作需要,最近開始接觸MongoDB,對于一個已經習慣了傳統SQL的程序員來說,進入NoSQL可以說是需要莫大的勇氣的。最大的不適應,是NoSQL和SQL的存儲原理與思維方式的不一致。當然,筆者也是NoSQL中的菜鳥,在這里分享一下自己的一些學習總結與心得。
1、數組
在MongoDB中,文檔(Document)即表示數據庫中的一個集合中的一條記錄,相當于關系型數據庫中的行(row)。在MongoDB中,數組是使用JSON語法表示的,在MongoDB中,也稱為BSON格式。數組既可以作為有序對象來操作,也可以作為無序對象來操作。有序對象比如列表等,無序對象比如集合等等。
{
"a" : 0, //這明顯不是一個數組
"b" : [], //定義一個空的數組
"c" : [ "1","2","3"] //包含3個元素的數組
}
2、文檔嵌套
在NoSQL中,文檔與文檔之間是可以隨意嵌套的,比如某個字段的值為某個類型的對象。理論上,MongoDB支持無限級的自我嵌套,比如:
{
"a": {"b":1,"c":2}
}
3、文檔標識
如果標記當前這個文檔,按照關系型數據庫的習慣,則可以說,如果保證一條記錄在數據庫里是唯一的,在傳統SQL中,使用
主鍵ID
來標識記錄的全局唯一性,而在MongoDB中,則使用ObjectId
來確保文檔的唯一標識,即_id
的默認類型。
ObjectID是一個12字節的BSON數據類型,格式如下:
- 前四個字節表示時間戳
- 接下來三個字節是機器標識碼
- 緊接的兩個字節由進程ID組成,即PID
- 最后三個字節是隨機數
MongoDB中的每個文檔必須有一個名為_id
的鍵,可以是任意類型,默認為ObjectID類型。
以下是在Shell中關于ObjectId的一些方法
#生成一個新的ObjectId
newObjectId = ObjectId()
#返回的id為:ObjectId("5349b4ddd2781d08c09890f3")
#獲取文檔
ObjectId("5349b4ddd2781d08c09890f3").getTimestamp()
#返回時間為:ISODate("2016-07-06T21:49:17Z")
#將ObjectId轉化為字符串
new ObjectId().str
#返回結果為:5349b4ddd2781d08c09890f3
3、添加文檔
插入文檔使用
db.col.insert(document)
。
db.Collection_Name.insert({
"name":"demo"
});
- 如果文檔不包含_id鍵,MongoDB會自動創建一個ObjectId類型的_id值
- 默認情況下插入操作時,MongoDB只檢查傳入數據是否包含_id以及數據大小是否超過16MB,所以可以得到更高的性能插入,但同時也可能錄入無效數據。
- 因為在插入時是不執行任何代碼的,所以與傳統SQL相比,MongoDB不存在SQL注入風險。
4、刪除文檔
db.Collection_Name.remove(); //清空集合內的所有文檔
db.Collection_Name.remove( //清空指定文檔
{
"a":"a"
}
);
//當集合內數據過多時,可以考慮下面這個方法
db.drop_collection(Collection_Name); //直接刪除集合
db.Collection_Name.ensureIndex(); //重建索引
5、更新文檔
//update語法定義
db.Collection_Name.update(query,document,upsert,multi);
//設定原文檔為:
var data = {_id:"xxxx","a":1,"b":2};
//query 是指查詢條件,相當于SQL中的where子句,比如:
db.Collection_Name.update(
{_id:"xxxx","a":1,"b":2}, //定位條件,對符合_id=xxxx,a=1,b=2的文檔進行更新
{"a":2}, //將a的值改為2,替換整個文檔
true, //若查詢不到符合條件的文檔,則新增一個文檔
true //允許更新多行
);
- update操作會替換整個匹配的文檔。而不是進行某些特定字段的修改。如果需要更新某個特定字段值,則應當使用修改器。
- upsert模式是一個布爾值選項,表示是否文檔更新時,如果不存在,能夠自動創建。
- multi模式也是一個布爾值選項,默認情況下只更新匹配到的第一個文檔,開啟了multi模式后(即設置為true),則會更新所有匹配的文檔。
//更新文檔使用到的一些修改器,由$符號定義
//$inc 增加或減少數字的值,鍵不存在時自動創建
db.Collection_Name.update(
{"name" : "翹著二郎腿打代碼"},
{"$inc" : { "lover" : 1 }} //只將lover字段的值加1
);
//$set 設置某一項或者多個項目的值
db.Collection_Name.update(
{"name" : "翹著二郎腿打代碼"},
{"$set" : {"name" : "打代碼" }}
);
這里列舉一些常用的修改器
$inc 設置自增或者自減
$set 設置指定鍵的值
$unset $set的反操作,會刪除鍵及鍵值
$push 將元素追加到數組末尾,數組不存在則自動創建
$pushAll $push的批量操作版本
$addToSet 與$push一樣,會自動過濾重復元素
$pop 從數組中移除元素,1代表從末尾移除,-1代碼從開頭移除
$pull 從數組中移除所有匹配的元素
$pullAll $pull的批量操作版本
$rename 修改制定鍵的鍵名
$bit 對整型鍵進行位操作
另外,還有一種方式可以實現文檔的更新:
//使用findAndModify()更新文檔
db.Collection_Name.findAndModify(
{
'query' : {"name" : "翹著二郎腿打代碼" },
'update' : {"$set" : { "favour" : 100 } },
'new' : true
}
);
/**
其中的參數如下:
query : 查詢條件,用來定位到匹配的文檔
sort : 如果匹配到多個文檔,指定一個排序方式,-1降序,1升序
remove : 是否刪除匹配的文檔
new : 是否返回更新后的文檔
update : 更新操作
upsert : 是否自動創建,如果匹配不到文檔
**/
save()
//文檔不存在時,執行insert操作,存在是執行update操作。
db.demo.save(document);
6、查詢
我們先來舉一些簡單的例子:
//select * from demo;
db.demo.find();
//select * from demo where a = 1;
db.demo.find({"a":1});
//select a,b from demo where a = 1;
db.demo.find({"a":1},{a:1,b:1});
//select * from demo where a = 1 order by name asc;
db.demo.find({"a":1}).sort({"name":1});
//select * from demo where a > 1;
db.demo.find({"a":{$gt:1}});
//select * from demo where a like 'eee';
db.demo.find({"name":"/^eee/"});
//select * from demo limit 10 skip 20;
db.demo.find().limit(10).skip(20);
當然還有很多其他的語法形式,這里不再一一列舉。下面列舉一些常見的查詢條件操作符
$lt #小于
$lte #小于等于
$gt #大于
$gte #大于等于
$all #完全匹配
$mod #取模
$ne #不等于
$in #在...內
$nin #不在...內
$nor #既不...也不...
$or #或
$size #匹配數組長度
$type #匹配數據類型
slice()函數用于數組的查詢
db.demo.find({},{favours:{'$slice':1}}); //僅返回數組中的前1項
db.demo.find({},{favours:{'$slice':-1}}); //僅返回數組中的最后一項
db.demo.find({},{favours:{'$slice':[1,2]}}); //跳過前1項,返回接下來的10項
db.demo.find({},{favours:{'$slice':[-1,1]}});//跳過最后一項,返回接下來的1項
7、游標
MongoDB中的游標已經在各個版本的驅動程序中封裝好了,不需要像傳統SQL那樣使用PL/SQL結構化編程來聲明游標,在Shell中,游標的使用方式與Java中的迭代器十分相似。
var cursor = db.demo.find(); //聲明游標
while(cursor.hasNext()){ //遍歷集合
var element = cursor.next();
}
8、$WHERE
$where
操作符也是用來定位查詢的,這個SQL中的where非常類似,前面我們說過,匹配文檔的時候可以使用各種查詢條件操作符來實現,但是為什么還要有這個操作符呢?因為有些查詢是無法通過之前講過 的那些查詢操作符來實現的。值得注意的是,$where操作符的性能低,沒有使用也無法使用索引機制。
//傳統SQL
select * from demo where a > 1;
//使用查詢操作符實現
db.demo.find({a:{"$gt":1}});
//使用$where實現
db.demo.find({"$where":"this.a > 1"});
db.demo.find("this.a > 1");
db.demo.find(function(){
return this.a > 1;
});
9、 排序&分頁
MongoDB中提供了相關的方法進行排序和分頁,主要有
limit()
,skip()
和sort()
//每頁10條記錄,略過前面10條記錄,按a降序排序
db.demo.find().limit(10).skip(10).sort({a:-1});
10、索引
MongoDB的索引機制與傳統SQL的索引基本上是一樣的。
//創建索引
db.demo.ensureIndex({'a':1});
//創建子文檔索引
db.demo.ensureIndex({'a.b':-1});
//創建復合索引
db.demo.ensureIndex({
"a":1,"b":-1
});
//在MongoDB中,1表示升序,-1表示降序
//重新索引,一般是修改索引后重新索引操作
db.demo.reIndex();
//刪除索引
db.demo.dropIndexes();
11、聚合
count()
//select count(a) from demo where a = 1;
db.demo.count({"a":1});
distinct()
db.demo.distinct("zip-code",{a:1});
group(key,cond,reduce,initial)
其中,
key :分組依據
cond: 查詢條件
reduce:聚合操作
initial : 指定聚合計數器的初始對象
//sql表示
select a,b,sum(c) from demo where a = 1 group by a,b;
//MongoDB表示
db.demo.gourp({
"key":{
"a": true,
"b": true
},
"cond":{
"a":1
}
});
12、歸納一下MongoDB中的一些Tips
1、MongoDB與傳統SQL的顯著區別
SQL | MongoDB |
---|---|
表(Table) | 集合(Collection) |
行(row) | 文檔(document) |
2、集合不能以
system.
開頭, 這是因為MongoDB中的系統集合保持的前綴。比如
db.system.update(document); //錯誤
3、 ObjectId類型是
_id
的默認類型,也可以自己指定其數據類型。MongoDB的初衷是設計成一個分布式的數據庫,所以不會自動實現_id
的自增。插入文檔時,如果沒有指定_id
的值,則系統自動創建一個ObjectId類型的值,一般在客戶端的驅動程序中完成。
4、插入文檔時,MongoDB會解析BSON數據,BSON數據格式與JSON基本一致,在MongoDB中稱為BSON。插入時會檢查是否包含
_id
以及檢查文檔數據是否超過16MB
,其余全部不作檢查,從而實現其高效率性。
5、MongoDB在插入數據時,不會執行插入數據的代碼,而是將BSON數據直接寫入,不作任何的數據驗證,所以不存在類似于SQL中的注入風險。
說明
本篇文章也是筆者自己在學習過后總結出來的。也是針對習慣于傳統SQL的簡友們寫的,傳統的SQL數據庫與NoSQL個人感覺差別還算是挺大的。剛開始接觸NoSQL的時候最大的不適應就是在MongoDB中是不需要設計表和表結構的,全是基于JSON的數據操作,所以其邏輯原理都在程序代碼中實現,而MongoDB本身只負責分布式的數據存儲。
文章更新日志
2016-07-08 文章初稿