我在MongoDB使用中遇到的幾個(gè)問題

N久以前,在簡書上寫一了篇MongoDB的技術(shù)文。因?yàn)橄虢o大家以及身邊的朋友帶來一點(diǎn)干貨,一直沒發(fā)布,直到……我想發(fā)的時(shí)候才發(fā)現(xiàn)很多內(nèi)容已經(jīng)隨處可見了,只好在此基礎(chǔ)上修改、刪減。這篇文章分享下我在實(shí)際使用中遇到的問題,是我在某個(gè)時(shí)間的所思所想。如有問題歡迎指正,不勝感激!

MongoDB
MongoDB

1.MongoDB主鍵_id

在我一開始接觸MongoDB的時(shí)候我覺得無論是什么數(shù)據(jù)庫都是有主鍵的,而且可以自定義,直到遇到了MongoDB。沒有人會(huì)喜歡被強(qiáng)制使用“_id”這樣的主鍵名稱,MongoBlog上面是這樣說的:

blog.mongodb.org:Every document stored in MongoDB must have an "_id" key, and you can have other keys as many as your wish.

MongoDB默認(rèn)會(huì)為每個(gè)document生成一個(gè) _id 屬性,作為默認(rèn)主鍵,且默認(rèn)值為ObjectId,可以更改 _id 的值(可為空字符串),但每個(gè)document必須擁有 _id 屬性。如果你不想使用默認(rèn)的主鍵你需要插入自己的主鍵名和值(自行管理、維護(hù)),例如:userId: 20140600001。
為什么是這樣的?粗略說來大概是這么一句話:MongoDB是以空間換取時(shí)間和效率,本身不提供關(guān)系數(shù)據(jù)庫中常見的主鍵生成策略、事務(wù)。因此使用者或者說服務(wù)器端需要有自己的處理邏輯。

2.時(shí)間

MongoDB中的時(shí)間類型默認(rèn)是MongoDate,MongoDate默認(rèn)是按照UTC(世界標(biāo)準(zhǔn)時(shí)間)來存儲(chǔ)。例如下面的兩種使用方式:

db.col.insert({"date": new Date(),num: 1})
db.col.insert({"date": new Date().toLocaleString(),num: 2})

db.col.find()
{
    "_id" : ObjectId("539944b14a696442d95eaf08"),
    "date" : ISODate("2014-06-12T06:12:01.500Z"),
    "num" : 1
}
{
    "_id" : ObjectId("539944b14a696442d95eaf09"),
    "date" : "Thu Jun 12 14:12:01 2014",
    "num" : 2
}

注意:第一條數(shù)據(jù)存儲(chǔ)的是一個(gè)Date類型,第二條存儲(chǔ)存儲(chǔ)的是String類型。兩條數(shù)據(jù)的時(shí)間相差大約8個(gè)小時(shí)(忽略操作時(shí)間),第一條數(shù)據(jù)MongoDB是按照UTC時(shí)間來進(jìn)行存儲(chǔ)。

另外還有朋友說構(gòu)造日期時(shí)要指定完整的時(shí)間,否則也將會(huì)造成時(shí)間不準(zhǔn)確。對(duì)于這個(gè),我做了嘗試并沒有出現(xiàn)這種情況,是否是版本問題不得而知……
一般,我們?cè)谑褂肕ongoDB存儲(chǔ)時(shí)間的時(shí)候會(huì)把時(shí)間存儲(chǔ)成字符串(或者直接存儲(chǔ)為毫秒數(shù)),將MongoDB內(nèi)置處理的這部分內(nèi)容完全交由服務(wù)器來做,這樣也能保證所有的時(shí)間格式統(tǒng)一、邏輯統(tǒng)一。

3.MongoDB中的一對(duì)多、多對(duì)多關(guān)系(MongoDB中數(shù)據(jù)關(guān)系的處理)

MongoDB的基本單元是Document(文檔),通過文檔的嵌套(關(guān)聯(lián))來組織、描述數(shù)據(jù)之間的關(guān)系。
例如我們要表示一對(duì)多關(guān)系,在關(guān)系型數(shù)據(jù)庫中我們通常會(huì)設(shè)計(jì)兩張表A(一)、B(多),然后在B表中存入A的主鍵,以此做關(guān)聯(lián)關(guān)系。然后查詢的時(shí)候需要從兩張表分別取數(shù)據(jù)。MongoDB中的Document是通過嵌套來描述數(shù)據(jù)之間的關(guān)系,例如:

{
    _id:ObjectId(akdjfiou23o4iu23oi5jktlksdjfa)
    teacherName: "foo",
    students: [
        {
            stuName: "foo",
            totalScore:100,
            otherInfo :[]
            ...
        },{
            stuName: "bar",
            totalScore:90,
            otherInfo :[]
            ...
        }
    ]
}

一次查詢便可得到所有老師和同學(xué)的對(duì)應(yīng)關(guān)系。當(dāng)然我們還可以進(jìn)一步的進(jìn)行子查詢,例如我們可以選擇totalScore在100以上的學(xué)生,包括再對(duì)students進(jìn)行分頁等等。

注意:不是所有的一對(duì)多關(guān)系都適合這種組織形式

如何處理好多對(duì)多的關(guān)系可謂是NoSQL的精髓所在。理論上,可以在一個(gè)集合中完成存儲(chǔ),不過實(shí)際上這樣的情況非常罕見。這是由于查詢的多樣性所導(dǎo)致的,若是只有一種類型的查詢,則這種多對(duì)多的關(guān)系放在一個(gè)良好設(shè)計(jì)的集合中,雖然會(huì)有大量的冗余,但是效率一定是最高的。如何設(shè)計(jì)這種數(shù)據(jù)庫的關(guān)鍵就是看你有多少種查詢,每一種的頻率是多少,使用的其他要求是什么樣的。對(duì)于不同的查詢,同樣的數(shù)據(jù)庫設(shè)計(jì)的性能也是大不一樣。還有一點(diǎn),一般不要拆成三個(gè)集合,這是傳統(tǒng)的關(guān)系型數(shù)據(jù)庫的思維方式。傳統(tǒng)關(guān)系型數(shù)據(jù)庫是為了防止冗余,而MongoDB優(yōu)良的存取速度以及分片結(jié)構(gòu)恰恰可以應(yīng)對(duì)數(shù)據(jù)冗余。NoSQL(Not Only SQL)數(shù)據(jù)庫一定要換個(gè)角度思考問題。常見的情況就是拆成兩個(gè)集合,然后有一部分冗余,對(duì)最常用的查詢做一個(gè)索引。這個(gè)這個(gè)還得多多實(shí)踐。

4.內(nèi)嵌文檔查詢

在MongoDB中文檔的查詢是與順序有關(guān)的。例如:

{
    "address" : {
        "province" : "河北省",
        "city" : "石家莊",
        ...
    },
    "number" : 2640613
}

要搜索province為“河北省”、city為“石家莊”可以這樣:

db.col.find(
    {
        "address":{
            "city" : "石家莊",
            "province" : "河北省"
        }
    }
)

然而這樣什么都不會(huì)查詢到。事實(shí)上,這樣的查詢MongoDB會(huì)當(dāng)做全量匹配查詢,即document中所有屬性與查詢條件全部一致時(shí)才會(huì)被返回。當(dāng)然這里的“全部一致”也包括屬性的順序。那么,上面的查詢?nèi)绻胨阉鞯街暗膽?yīng)該先補(bǔ)充number屬性,然后更改address屬性下的順序。
在實(shí)際應(yīng)用中我們當(dāng)然不會(huì)這么來查詢文檔,尤其是需要查詢內(nèi)嵌文檔的時(shí)候。MongoDB中提供"."(點(diǎn))表示法來查詢內(nèi)嵌文檔。因此,上面的查詢可以這樣寫:

db.col.find(
    {
        "address.privince":"河北省"
    }
)

然而此時(shí)我們卻引入了另一個(gè)問題,例如將URL作為鍵保存的時(shí)候。一種解決辦法是在插入前執(zhí)行一次替換,轉(zhuǎn)換為URL中的非法字符。
當(dāng)文檔結(jié)構(gòu)變得更加復(fù)雜以后,內(nèi)嵌文檔的匹配需要些許技巧。例如,假設(shè)有博客文章若干,要找到由Joe發(fā)表的5分以上的評(píng)論。博客文章的結(jié)構(gòu)如下例所示:

{
    "content" : "...",
    "comments" : [
        {
            "author" : "joe",
            "score" : 3,
            "comment" : "nice post"
        },
        {
           "author" : "mary",
           "score" : 6,
           "comment" : "terrible post"
        }
    ]
}

不能直接用db.blog.find({"comments":{"author":"joe","score":{"$gte":5}}})來查尋。內(nèi)嵌文檔匹配要求整個(gè)文檔完全匹配,而這不會(huì)匹配"comment"鍵。使用db.blog.find({"comments.author" : "joe", "comments.score" : {"$gte" : 5}})同樣也不會(huì)達(dá)到目的。因?yàn)榉蟖uthor條件的評(píng)論和符合score條件的評(píng)論可能不是同一條評(píng)論。也就是說,會(huì)返回剛才顯示的那個(gè)文檔。因?yàn)?author" : "joe"在第一條評(píng)論中匹配了,"score" : 6在第二條評(píng)論中匹配了。
要正確地指定一組條件,而不用指定每個(gè)鍵,要使用"$elemMatch"。這種模糊的命名條件句能用來部分指定匹配數(shù)組中的單個(gè)內(nèi)嵌文檔的限定條件。所以正確的寫法應(yīng)該是這樣的:

db.blog.find(
    {
        "comments" : {
            "$elemMatch" : {
                "author" : "joe",
                "score":{
                    "$gte":5
                }
            }
        }
    }
)

"$elemMatch"將限定條件進(jìn)行分組,僅當(dāng)需要對(duì)一個(gè)內(nèi)嵌文檔的多個(gè)鍵操作時(shí)才會(huì)用到。

MongoDB安全

MongoDB在默認(rèn)設(shè)置下并沒有身份驗(yàn)證。MongoDB會(huì)認(rèn)為自身處在一個(gè)擁有防火墻的信任網(wǎng)絡(luò)。但是這不代表它不支持身份驗(yàn)證,如果需要可以輕松的開啟(啟動(dòng)參數(shù)auth)。
還有一點(diǎn)就是,與MongoDB的連接默認(rèn)情況下都是非加密的,這就意味你的數(shù)據(jù)可能被第三方記錄和使用。如果你的MongoDB是在自己的非廣域網(wǎng)下使用,那么這種情況是不可能發(fā)生的。
然而如果你是通過公網(wǎng)訪問MongoDB的話,那么你肯定會(huì)希望你的通信是經(jīng)過加密的。由于各種原因,公版的MongoDB是不支持SSL的,如果需要使用帶有SSL的MongoDB需要自行編譯,私人定制一個(gè)自己的MongoDB。企業(yè)版本肯定是含有這個(gè)功能的,不過最少也要掏出2500刀吧。
如何編譯帶有SSL支持的MongoDB可以參考這篇文章:
Compiling MongoDB with SSL Support on Ubuntu

參考資料

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時(shí)請(qǐng)結(jié)合常識(shí)與多方信息審慎甄別。
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡書系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

友情鏈接更多精彩內(nèi)容