0 由于工作需要咽袜,最近開(kāi)始接觸MongoDB妨马,對(duì)于一個(gè)已經(jīng)習(xí)慣了傳統(tǒng)SQL的程序員來(lái)說(shuō),進(jìn)入NoSQL可以說(shuō)是需要莫大的勇氣的弱恒。最大的不適應(yīng)辨萍,是NoSQL和SQL的存儲(chǔ)原理與思維方式的不一致。當(dāng)然返弹,筆者也是NoSQL中的菜鳥(niǎo)锈玉,在這里分享一下自己的一些學(xué)習(xí)總結(jié)與心得。
1义起、數(shù)組
在MongoDB中拉背,文檔(Document)即表示數(shù)據(jù)庫(kù)中的一個(gè)集合中的一條記錄,相當(dāng)于關(guān)系型數(shù)據(jù)庫(kù)中的行(row)默终。在MongoDB中椅棺,數(shù)組是使用JSON語(yǔ)法表示的,在MongoDB中齐蔽,也稱為BSON格式两疚。數(shù)組既可以作為有序?qū)ο髞?lái)操作,也可以作為無(wú)序?qū)ο髞?lái)操作含滴。有序?qū)ο蟊热缌斜淼扔詹常瑹o(wú)序?qū)ο蟊热缂系鹊取?/p>
{
"a" : 0, //這明顯不是一個(gè)數(shù)組
"b" : [], //定義一個(gè)空的數(shù)組
"c" : [ "1","2","3"] //包含3個(gè)元素的數(shù)組
}
2、文檔嵌套
在NoSQL中蛙吏,文檔與文檔之間是可以隨意嵌套的源哩,比如某個(gè)字段的值為某個(gè)類(lèi)型的對(duì)象。理論上鸦做,MongoDB支持無(wú)限級(jí)的自我嵌套励烦,比如:
{
"a": {"b":1,"c":2}
}
3、文檔標(biāo)識(shí)
如果標(biāo)記當(dāng)前這個(gè)文檔泼诱,按照關(guān)系型數(shù)據(jù)庫(kù)的習(xí)慣坛掠,則可以說(shuō),如果保證一條記錄在數(shù)據(jù)庫(kù)里是唯一的治筒,在傳統(tǒng)SQL中屉栓,使用
主鍵ID
來(lái)標(biāo)識(shí)記錄的全局唯一性,而在MongoDB中耸袜,則使用ObjectId
來(lái)確保文檔的唯一標(biāo)識(shí)友多,即_id
的默認(rèn)類(lèi)型。
ObjectID是一個(gè)12字節(jié)的BSON數(shù)據(jù)類(lèi)型堤框,格式如下:
- 前四個(gè)字節(jié)表示時(shí)間戳
- 接下來(lái)三個(gè)字節(jié)是機(jī)器標(biāo)識(shí)碼
- 緊接的兩個(gè)字節(jié)由進(jìn)程ID組成域滥,即PID
- 最后三個(gè)字節(jié)是隨機(jī)數(shù)
MongoDB中的每個(gè)文檔必須有一個(gè)名為_id
的鍵纵柿,可以是任意類(lèi)型,默認(rèn)為ObjectID類(lèi)型启绰。
以下是在Shell中關(guān)于ObjectId的一些方法
#生成一個(gè)新的ObjectId
newObjectId = ObjectId()
#返回的id為:ObjectId("5349b4ddd2781d08c09890f3")
#獲取文檔
ObjectId("5349b4ddd2781d08c09890f3").getTimestamp()
#返回時(shí)間為:ISODate("2016-07-06T21:49:17Z")
#將ObjectId轉(zhuǎn)化為字符串
new ObjectId().str
#返回結(jié)果為:5349b4ddd2781d08c09890f3
3昂儒、添加文檔
插入文檔使用
db.col.insert(document)
。
db.Collection_Name.insert({
"name":"demo"
});
- 如果文檔不包含_id鍵委可,MongoDB會(huì)自動(dòng)創(chuàng)建一個(gè)ObjectId類(lèi)型的_id值
- 默認(rèn)情況下插入操作時(shí)渊跋,MongoDB只檢查傳入數(shù)據(jù)是否包含_id以及數(shù)據(jù)大小是否超過(guò)16MB,所以可以得到更高的性能插入着倾,但同時(shí)也可能錄入無(wú)效數(shù)據(jù)拾酝。
- 因?yàn)樵诓迦霑r(shí)是不執(zhí)行任何代碼的,所以與傳統(tǒng)SQL相比屈呕,MongoDB不存在SQL注入風(fēng)險(xiǎn)微宝。
4、刪除文檔
db.Collection_Name.remove(); //清空集合內(nèi)的所有文檔
db.Collection_Name.remove( //清空指定文檔
{
"a":"a"
}
);
//當(dāng)集合內(nèi)數(shù)據(jù)過(guò)多時(shí)虎眨,可以考慮下面這個(gè)方法
db.drop_collection(Collection_Name); //直接刪除集合
db.Collection_Name.ensureIndex(); //重建索引
5、更新文檔
//update語(yǔ)法定義
db.Collection_Name.update(query,document,upsert,multi);
//設(shè)定原文檔為:
var data = {_id:"xxxx","a":1,"b":2};
//query 是指查詢條件镶摘,相當(dāng)于SQL中的where子句嗽桩,比如:
db.Collection_Name.update(
{_id:"xxxx","a":1,"b":2}, //定位條件,對(duì)符合_id=xxxx凄敢,a=1碌冶,b=2的文檔進(jìn)行更新
{"a":2}, //將a的值改為2,替換整個(gè)文檔
true, //若查詢不到符合條件的文檔涝缝,則新增一個(gè)文檔
true //允許更新多行
);
- update操作會(huì)替換整個(gè)匹配的文檔扑庞。而不是進(jìn)行某些特定字段的修改。如果需要更新某個(gè)特定字段值拒逮,則應(yīng)當(dāng)使用修改器罐氨。
- upsert模式是一個(gè)布爾值選項(xiàng),表示是否文檔更新時(shí)滩援,如果不存在栅隐,能夠自動(dòng)創(chuàng)建。
- multi模式也是一個(gè)布爾值選項(xiàng)玩徊,默認(rèn)情況下只更新匹配到的第一個(gè)文檔租悄,開(kāi)啟了multi模式后(即設(shè)置為true),則會(huì)更新所有匹配的文檔。
//更新文檔使用到的一些修改器恩袱,由$符號(hào)定義
//$inc 增加或減少數(shù)字的值泣棋,鍵不存在時(shí)自動(dòng)創(chuàng)建
db.Collection_Name.update(
{"name" : "翹著二郎腿打代碼"},
{"$inc" : { "lover" : 1 }} //只將lover字段的值加1
);
//$set 設(shè)置某一項(xiàng)或者多個(gè)項(xiàng)目的值
db.Collection_Name.update(
{"name" : "翹著二郎腿打代碼"},
{"$set" : {"name" : "打代碼" }}
);
這里列舉一些常用的修改器
$inc 設(shè)置自增或者自減
$set 設(shè)置指定鍵的值
$unset $set的反操作,會(huì)刪除鍵及鍵值
$push 將元素追加到數(shù)組末尾畔塔,數(shù)組不存在則自動(dòng)創(chuàng)建
$pushAll $push的批量操作版本
$addToSet 與$push一樣潭辈,會(huì)自動(dòng)過(guò)濾重復(fù)元素
$pop 從數(shù)組中移除元素,1代表從末尾移除纪吮,-1代碼從開(kāi)頭移除
$pull 從數(shù)組中移除所有匹配的元素
$pullAll $pull的批量操作版本
$rename 修改制定鍵的鍵名
$bit 對(duì)整型鍵進(jìn)行位操作
另外,還有一種方式可以實(shí)現(xiàn)文檔的更新:
//使用findAndModify()更新文檔
db.Collection_Name.findAndModify(
{
'query' : {"name" : "翹著二郎腿打代碼" },
'update' : {"$set" : { "favour" : 100 } },
'new' : true
}
);
/**
其中的參數(shù)如下:
query : 查詢條件萎胰,用來(lái)定位到匹配的文檔
sort : 如果匹配到多個(gè)文檔碾盟,指定一個(gè)排序方式,-1降序技竟,1升序
remove : 是否刪除匹配的文檔
new : 是否返回更新后的文檔
update : 更新操作
upsert : 是否自動(dòng)創(chuàng)建冰肴,如果匹配不到文檔
**/
save()
//文檔不存在時(shí),執(zhí)行insert操作榔组,存在是執(zhí)行update操作熙尉。
db.demo.save(document);
6鸟廓、查詢
我們先來(lái)舉一些簡(jiǎn)單的例子:
//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);
當(dāng)然還有很多其他的語(yǔ)法形式驶沼,這里不再一一列舉。下面列舉一些常見(jiàn)的查詢條件操作符
$lt #小于
$lte #小于等于
$gt #大于
$gte #大于等于
$all #完全匹配
$mod #取模
$ne #不等于
$in #在...內(nèi)
$nin #不在...內(nèi)
$nor #既不...也不...
$or #或
$size #匹配數(shù)組長(zhǎng)度
$type #匹配數(shù)據(jù)類(lèi)型
slice()函數(shù)用于數(shù)組的查詢
db.demo.find({},{favours:{'$slice':1}}); //僅返回?cái)?shù)組中的前1項(xiàng)
db.demo.find({},{favours:{'$slice':-1}}); //僅返回?cái)?shù)組中的最后一項(xiàng)
db.demo.find({},{favours:{'$slice':[1,2]}}); //跳過(guò)前1項(xiàng)苇经,返回接下來(lái)的10項(xiàng)
db.demo.find({},{favours:{'$slice':[-1,1]}});//跳過(guò)最后一項(xiàng)锨推,返回接下來(lái)的1項(xiàng)
7铅歼、游標(biāo)
MongoDB中的游標(biāo)已經(jīng)在各個(gè)版本的驅(qū)動(dòng)程序中封裝好了,不需要像傳統(tǒng)SQL那樣使用PL/SQL結(jié)構(gòu)化編程來(lái)聲明游標(biāo)换可,在Shell中椎椰,游標(biāo)的使用方式與Java中的迭代器十分相似。
var cursor = db.demo.find(); //聲明游標(biāo)
while(cursor.hasNext()){ //遍歷集合
var element = cursor.next();
}
8沾鳄、$WHERE
$where
操作符也是用來(lái)定位查詢的慨飘,這個(gè)SQL中的where非常類(lèi)似,前面我們說(shuō)過(guò)译荞,匹配文檔的時(shí)候可以使用各種查詢條件操作符來(lái)實(shí)現(xiàn)瓤的,但是為什么還要有這個(gè)操作符呢?因?yàn)橛行┎樵兪菬o(wú)法通過(guò)之前講過(guò) 的那些查詢操作符來(lái)實(shí)現(xiàn)的吞歼。值得注意的是圈膏,$where操作符的性能低,沒(méi)有使用也無(wú)法使用索引機(jī)制浆熔。
//傳統(tǒng)SQL
select * from demo where a > 1;
//使用查詢操作符實(shí)現(xiàn)
db.demo.find({a:{"$gt":1}});
//使用$where實(shí)現(xiàn)
db.demo.find({"$where":"this.a > 1"});
db.demo.find("this.a > 1");
db.demo.find(function(){
return this.a > 1;
});
9本辐、 排序&分頁(yè)
MongoDB中提供了相關(guān)的方法進(jìn)行排序和分頁(yè),主要有
limit()
,skip()
和sort()
//每頁(yè)10條記錄医增,略過(guò)前面10條記錄慎皱,按a降序排序
db.demo.find().limit(10).skip(10).sort({a:-1});
10、索引
MongoDB的索引機(jī)制與傳統(tǒng)SQL的索引基本上是一樣的叶骨。
//創(chuàng)建索引
db.demo.ensureIndex({'a':1});
//創(chuàng)建子文檔索引
db.demo.ensureIndex({'a.b':-1});
//創(chuàng)建復(fù)合索引
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 :分組依據(jù)
cond: 查詢條件
reduce:聚合操作
initial : 指定聚合計(jì)數(shù)器的初始對(duì)象
//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與傳統(tǒng)SQL的顯著區(qū)別
SQL | MongoDB |
---|---|
表(Table) | 集合(Collection) |
行(row) | 文檔(document) |
2些阅、集合不能以
system.
開(kāi)頭, 這是因?yàn)镸ongoDB中的系統(tǒng)集合保持的前綴斑唬。比如
db.system.update(document); //錯(cuò)誤
3市埋、 ObjectId類(lèi)型是
_id
的默認(rèn)類(lèi)型,也可以自己指定其數(shù)據(jù)類(lèi)型恕刘。MongoDB的初衷是設(shè)計(jì)成一個(gè)分布式的數(shù)據(jù)庫(kù)缤谎,所以不會(huì)自動(dòng)實(shí)現(xiàn)_id
的自增。插入文檔時(shí)褐着,如果沒(méi)有指定_id
的值坷澡,則系統(tǒng)自動(dòng)創(chuàng)建一個(gè)ObjectId類(lèi)型的值,一般在客戶端的驅(qū)動(dòng)程序中完成含蓉。
4频敛、插入文檔時(shí),MongoDB會(huì)解析BSON數(shù)據(jù)谴餐,BSON數(shù)據(jù)格式與JSON基本一致姻政,在MongoDB中稱為BSON。插入時(shí)會(huì)檢查是否包含
_id
以及檢查文檔數(shù)據(jù)是否超過(guò)16MB
,其余全部不作檢查岂嗓,從而實(shí)現(xiàn)其高效率性。
5鹊碍、MongoDB在插入數(shù)據(jù)時(shí)厌殉,不會(huì)執(zhí)行插入數(shù)據(jù)的代碼,而是將BSON數(shù)據(jù)直接寫(xiě)入侈咕,不作任何的數(shù)據(jù)驗(yàn)證公罕,所以不存在類(lèi)似于SQL中的注入風(fēng)險(xiǎn)。
說(shuō)明
本篇文章也是筆者自己在學(xué)習(xí)過(guò)后總結(jié)出來(lái)的耀销。也是針對(duì)習(xí)慣于傳統(tǒng)SQL的簡(jiǎn)友們寫(xiě)的楼眷,傳統(tǒng)的SQL數(shù)據(jù)庫(kù)與NoSQL個(gè)人感覺(jué)差別還算是挺大的。剛開(kāi)始接觸NoSQL的時(shí)候最大的不適應(yīng)就是在MongoDB中是不需要設(shè)計(jì)表和表結(jié)構(gòu)的熊尉,全是基于JSON的數(shù)據(jù)操作罐柳,所以其邏輯原理都在程序代碼中實(shí)現(xiàn),而MongoDB本身只負(fù)責(zé)分布式的數(shù)據(jù)存儲(chǔ)狰住。
文章更新日志
2016-07-08 文章初稿