MongoDB
這幾天編寫程序,發(fā)現(xiàn)如果沒有理論的支持,即使時(shí)間花的再多,效率也是不高的,所以每天在編程之前都應(yīng)該先給自己充一下電。這次重新看了mongoDB權(quán)威開發(fā)指南的前四章,做了下面的讀書筆記。
- mongoDB 是面向文檔的數(shù)據(jù)庫,不是關(guān)系型數(shù)據(jù)庫;
- 文檔是MongoDb中數(shù)據(jù)的基本單元;
- 每一個(gè)文檔都有一個(gè)特殊的鍵"_id",在文檔所處的集合中是唯一的??梢宰鳛槲臋n的唯一標(biāo)識符
文檔
文檔是MongoDB的核心概念。包括多個(gè)鍵和關(guān)聯(lián)的值,有序地存放在一起;
{"greeting": "Hello world!"};
- 鍵: greeting;
- 值: hello world;
多個(gè)鍵值對:
{
"greeting": "hello world", //字符串
"foo": "2" //整形
}
//另外的一個(gè)鍵值對
{
"foo": "2",
"greeting": "hello world"
}
文檔中的鍵/值對必須是有序的,上面兩個(gè)是不同的鍵值對
字符串作為鍵的要求:
- 鍵不能含有
\0空字符,這個(gè)字符用來保存鍵的結(jié)尾 -
.和$有特別的含義,也不可以 -
_開頭的鍵是保留的 - MongoDB 區(qū)分類型,也區(qū)分大小寫,不能有重復(fù)的鍵
集合是多個(gè)鍵值對的集合
mongoDB的使用
- mongoDB在沒有參數(shù)的情況下會默認(rèn)數(shù)據(jù)目錄為/data/db
- 默認(rèn)情況下,mongoDB監(jiān)聽27017端口
- mongod還會啟動一個(gè)基本的http服務(wù)器,監(jiān)聽28017端口
- 可以通過瀏覽器訪問http://localhost:28017來獲取數(shù)據(jù)庫的管理信息
- shell下可以輸入
ctrl+C來停止Mongd的運(yùn)行
MongoDB中存儲的文檔必須有一個(gè)_id值。
- 這個(gè)值可以是任意類型的,默認(rèn)是
ObjectId對象。 - 在一個(gè)集合里面,每一個(gè)文檔都有一個(gè)唯一的
_id值,來確保集合里面的每一個(gè)文檔都能被唯一標(biāo)識 -
ObjectID是_id的默認(rèn)類型 - 在插入文檔的時(shí)候沒有
_id,系統(tǒng)會自動幫你創(chuàng)建一個(gè)。
強(qiáng)大的shell操作
-
insert添加一個(gè)文檔到集合里面
db.數(shù)據(jù)庫名.insert(自己定義好的一條數(shù)據(jù))
-
find查找數(shù)據(jù)庫,返回集合里面的所有文檔
db.數(shù)據(jù)庫名.find()
-
findOne(),返回?cái)?shù)據(jù)庫里面的一個(gè)文檔
db.數(shù)據(jù)庫名.findOne()
-
update()更改數(shù)據(jù)庫,接受至少兩個(gè)參數(shù),一個(gè)是更新的文檔限定條件,一個(gè)是新的文檔
db.blog.update({title:"my blog"}, post)
-
remove()刪除,沒有參數(shù)會刪除所有的文檔,一般接受一個(gè)條件
db.blog.remove({title:"my blog"})
- 移除blog中所有opt-out為true的人
db.blog.remove({"opt-out": true});
- 刪除數(shù)據(jù)是永久性的,不能撤銷也不能恢復(fù)
更改器的使用
-
$inc修改器增加pageviews的值
//將name為1對應(yīng)的文檔中的pageview增加1
db.analytics.update({"name":"1"}, {"$inc": {"pageview": 1}});
注意:使用修改器不能修改_id的值。
-
$set修改器用來指定一個(gè)鍵的值,如果這個(gè)鍵不存在,那么就創(chuàng)建它;
//users表中username為why的文檔中的favoriteBook設(shè)置為c
db.users.update({"username": "why"}, {"$set": {"favoriteBook": "c"}});
-
$set甚至可以修改鍵的數(shù)據(jù)類型,$set還可以修改內(nèi)嵌的屬性
//將favoirteBook的鍵值設(shè)置為一個(gè)數(shù)組
db.users.update({"username": "why"},{"$set": {"favoriteBook": ["c","c++"]}});
-
$unset可以完全刪除字段
db.users.update({"username": "why"},{"$unset": {"favoriteBook": "c"}});
-
$inc可以累計(jì)一個(gè)屬性,如果不存在,那么會事先創(chuàng)建一個(gè)新的屬性
//可以自己給它創(chuàng)建一個(gè)score: 50的屬性
{"$inc": {"score": 50}}
//score+1
{"$inc": {"score": 1}}
//結(jié)果將變?yōu)閟core: 51;
$inc只能用來修改數(shù)字,如果想要改變其他類型的值,可以選擇用$set
數(shù)組的操作
-
$push給已有的數(shù)組末尾添加一個(gè)元素, - 要是沒有這個(gè)數(shù)組,會自動創(chuàng)建一個(gè)新的數(shù)組
- 繼續(xù)添加元素,只需要再次使用
$push - 如果一個(gè)值不再數(shù)組里面,那么先用$ne來創(chuàng)建一個(gè)新的字段,再把它push到這個(gè)數(shù)組里面
db.user.update({"username": "{"$ne": "WHY"}"}, {$push:{"username": "WHY"}})
- 如果
$ne行不通,可以直接使用$addToSet,這樣還可以避免重復(fù)
db.users.update({"username":"why"},
{"$addToSet": {"emails":"qq.mail"}}
);
db.users.update({"username":"why"},
{"$addToSet": {"emails":{"qq.mail","126.com"}} }
);
將數(shù)組作為隊(duì)列或者是棧,可以使用
$pop這個(gè)修改器從數(shù)組的任何一端刪除元素
{$pop: {key: 1}}從數(shù)組末尾刪除一個(gè)元素{$pop: {key: -1}}從數(shù)組頭部刪除$pull會將數(shù)組中匹配的部分刪除掉
db.lists.insert({"todo": {"dishes": "dishes" ,"laundry","dry cleaning"}})
db.lists.update({}, {"$pull", "{"todo":"laundry"}");
db.list.find()
{
"_id": ObjectId("XXXX"),
"todo" : {
"dishes",
"dry cleaning"
}
}
對于數(shù)組[1,1,2,1]執(zhí)行pull 1 ,那么他會刪掉重復(fù)的字段
- 定位符
$
//將原先author為tom的字段修改為why
db.blog.update({"comments.author": "tom"},
{"$set": {"comments.$.author": "why"}});
$定位符之id那個(gè)匹配第一個(gè)匹配的元素。所以如果有多個(gè)評論人為tom的字段,只會修改第一個(gè)匹配的字段$upsert
db.math.remove()
db.math.upsert({"count": 25}, {"$inc": {"count": 3}}, true);
db.math.findOne() {
"id": ObjetcId(XXX);
"count": 28
}
先清空了集合,然后里面就沒有文檔,
再用upsert創(chuàng)建一個(gè)count的值為25的文檔
然后將這個(gè)值加3,最后得到count為28的文檔。
如果沒有開啟upsert的選項(xiàng),{"count" : 25}不會匹配到任何的文檔,就不會有修改
再次運(yùn)行,由于沒有{"count": 25}的選項(xiàng),那么他會再次創(chuàng)建一個(gè)count為25的字段,
然后再次+3為28
-
$save是保存
db.users.save();
更新多個(gè)文檔
- 默認(rèn)情況下,更新只能對符合匹配條件的第一個(gè)文檔執(zhí)行操作。
- 要是有多個(gè)文檔符合條件,其余的文檔就沒有變化。
- 要使得匹配到的文檔都得到更新,那么可以設(shè)置
update的第四個(gè)參數(shù)為true
db.users.update({"birthday": "10/13/2016"},
{$set: {gift: {"happy birthday"}}, false, true});
如果想知道文檔到底更新了多少,可以運(yùn)行getLastError命令
db.runCommand({getLastError: 1});
{
"err": null,
"updateExisting": true,
"n": 5,
"ok": true
}
- 這里的
n=5就說明有5個(gè)文檔被更新了。 -
updateExisting: true說明是對已有的文檔進(jìn)行更新
getLastError只能獲取更新的信息,不能返回已經(jīng)更新的文檔,
我們可以通過findAndModify獲取更新好的文檔,缺點(diǎn)是有點(diǎn)慢,需要等待數(shù)據(jù)庫的響應(yīng)
db.runCommand({
"findAndModify": "processes",
"query": {},
"sort": {},
"update": {}
})
- findAndModify: 字符串,集合的名字
- query: 查詢文檔,用來查詢文檔的條件
- sort: 排序的條件
- update: 修改器文檔,對所有找到的文檔執(zhí)行更新
- remove:布爾類型,表示是否刪除文檔
- new: 布爾類型,表示返回的是更新前的文檔還是更新后的文檔,默認(rèn)是更新前的文檔。
- update和remove必須有一個(gè),也只能有一個(gè),如果匹配不到文檔,那么則這個(gè)命令會返回一個(gè)錯(cuò)誤
- 一次只能處理一個(gè)文檔,也不能執(zhí)行upsert操作,只能更新已有的文檔
- 對于普通的更新來說,findAndModify速度比較慢,時(shí)間相當(dāng)一次查找,一次更新和一次getLastError
查詢
-
find查詢,查詢返回一個(gè)集合中文檔的子集,- 子集的范圍是從0個(gè)文檔到整個(gè)集合
- find的第一個(gè)參數(shù)決定要返回哪些文檔,其形式也是一個(gè)文檔,說明要查詢的細(xì)節(jié)
- 空的查詢文檔, 會返回集合的全部內(nèi)容,如果不指定查詢文檔。默認(rèn)就是空。
/這樣會返回集合c中的全部內(nèi)容
db.users.find{}
- 當(dāng)向查詢文檔中添加鍵值對時(shí),就以限定了查找的條件
查找方式是:1. 整數(shù)匹配整數(shù),2. 布爾值匹配布爾值, 3. 字符串匹配字符串。
//查詢所有年齡為27歲的用戶
db.users.find({"age": 27});
//查詢username為joe的字段
db.users.find({"username": "joe"});
//這樣是多字段查詢,會返回username為joe,年齡為27的所以字段
db.users.find({"username": "joe", "age":27})
指定返回的鍵
- 有時(shí)不需要將文檔中的所有鍵值對全部返回
- 可以通過
find()或者findOne()的第二個(gè)參數(shù)來指定想要的鍵 - 這樣可以節(jié)省傳輸?shù)臄?shù)據(jù)量,也可以節(jié)省客戶端解碼文檔的時(shí)間和內(nèi)存消耗
db.users.find({}, {"username": 1, "email": 1})
- 返回找到字段中的
username和email - 還有一個(gè)是
_id,這個(gè)鍵總是被返回,即使沒有指定_id顯示也是一樣 - 也可以通過第二個(gè)參數(shù)來剔除查詢結(jié)果中的某個(gè)鍵值對
db.users.find({}, {"password": 0});`
這樣返回的字段中就不會出現(xiàn)password這個(gè)鍵值對
查詢條件
-
$lt,$lte,$gt,$gte,是全部的比較操作符,分別對應(yīng)<, <=,>, >= - 可以將它們組合起來查詢一個(gè)范圍的值
//查詢年齡是18-30歲(含)的所有用戶
db.users.find({"age": {"$gte": 18, "$lte": 30}})
//可以查詢在現(xiàn)在這個(gè)時(shí)間之前注冊過的用戶
start = new Date();
db.users.find({"registerDate": {"$lt": start}})
-
$ne表示不等
//找到名字不是joe的用戶
db.users.find({"username": {"$ne":"joe"}})
$ne可以用于所有類型的數(shù)據(jù)
OR查詢
mongoDB有兩種方式進(jìn)行OR查詢,$in可以查詢一個(gè)鍵的多個(gè)值,
$or可以用來完成多個(gè)鍵值對的任意給定值(更加通用)
db.users.find({"username": {"$in":["why","joe"]}})
這回匹配username為why的文檔,也會匹配username為joe的文檔
如果$in中對于的數(shù)組只有一個(gè)值,那么這和直接匹配這個(gè)值得效果是一樣的
{ticket_no: {$in:[125]}}和{ticket_no: 125}是一樣的
與$in相反的是$nin,將返回與數(shù)組中所有條件都不匹配的文檔
db.users.find({"username": {"$nin":["why","joe"]}})
返回username既不是why,也不是joe的user
$in只能對單個(gè)鍵做OR查詢,而$or可以查詢包含所有可能條件的參數(shù)作為數(shù)組
db.raffie.find({"$or": [{"ticket_no": 125}, {"winner": true}]})
這樣會返回ticket_no"是125,winner是true的所有字段
$or還可以含有其他條件語句
db.raffie.find({"$or": [
{"ticket_no": ["$in":[123,124,125]]},
{"winner": true}
]})
條件句的規(guī)則
在查詢中,$lt在內(nèi)層文檔,而更新中$inc是外層文檔的鍵
條件句是內(nèi)層文檔的鍵,而修改器是外層文檔的鍵
一個(gè)鍵可以有多個(gè)條件,但是一個(gè)鍵不能對于多個(gè)更新更改器
//正確
db.users.find({"age": {"lt": 30, "$gt": 20})
//錯(cuò)誤
db.user.find({"$inc": {"age":1}, "$set": {age:40}})
null比較特殊,不僅僅匹配自身,而且還匹配不存在,所以我們在匹配鍵值為null的文檔的同時(shí),還要檢查該建筑是否存在
db.c.find({"z": {"$in":[null], "$exists": true}});
沒有$eq操作符,我們使用$in操作符代替
正則表達(dá)式
匹配名為Joe或者joe的用戶,可以用正則表達(dá)式匹配大小寫
db.users.find({"name":/joe/i})
正則表達(dá)式還可以插入到數(shù)據(jù)庫,自身也可以匹配
db.foo.insert({"bar": /baba/})
db.foo.find("bar": /baba/)
{
"_id": ObjectId("XXXXX"),
"bar": /baba/
}
查詢數(shù)組
- 插入一個(gè)數(shù)組
db.food.insert({"fruit":["apple", "banana"]})
db.food.find({"fruit":"banana"})
//找得到,但是比較低效
- $all 如果需要多個(gè)元素來匹配數(shù)組,那么就需要用到$all,這樣會匹配一組元素
db.find({"fruit": {$all: ["apple"]})
- $size 可以用來查詢指定長度的數(shù)組
db.food.({"fruit": {"$size":3}})
- $slice
find的第二個(gè)參數(shù)是可選的,可以返回那些鍵,"$slice"返回的是數(shù)組的一個(gè)子集合
//返回的是前10條評論
db.blog.posts.findOne(criteria, {"comments": {"$slice": 10}})
//-10表示的是后10條評論
db.blog.posts.findOne(criteria, {"comments": {"$slice": -10}})
//這個(gè)操作會跳過前面的前23個(gè)元素,返回第24個(gè)到第33個(gè)元素。
//如果數(shù)組不夠33個(gè)元素,那么會返回第23個(gè)元素后面的全部元素
db.blog.posts.findOne(criteria, {"comments": {"$slice": [23,10]}})
- limit()限制查詢的結(jié)果數(shù)量
//只返回3個(gè)結(jié)果
如果返回的結(jié)果不足3個(gè),那么返回匹配數(shù)量的結(jié)果。limit是上限而不是下線
db.c.find().limit(3);
//skip與limit類似
db.c.find().skip(3)
- sort是用一個(gè)對象作為參數(shù):
一組鍵值對,鍵對應(yīng)文檔的鍵名,值代表查詢的方向,排序方向可以是1升序-1降序 。 如果指定了多個(gè)鍵,那么按照鍵的順序逐個(gè)進(jìn)行進(jìn)行排序
db.c.find().sort({username: 1, age: -1})
簡單的分頁, 按照date的降序顯示文檔
var page1 = db.foo.find(cirterial).limit(100)
var latest = null;
while(page1.hasNext()) {
latest = page1.next();
display(latest);
}
//get next page
var page2 = db.foo.find({"date": {"$gt": latest.date}});
page2.sort({"date": -1}).limit(100);
唯一索引
db.people.ensureIndex({"username": 1}, {"unique": true});