MongoDB第三講 深入MongoDB的插入、刪除和更新操作

MongoDB的數(shù)組和對象類型

在MongoDB中可以添加數(shù)組和對象類型,這其實就對應(yīng)了關(guān)系數(shù)據(jù)庫中OneToMany和ManyToOne的類型,先來看如下一條數(shù)據(jù):

db.topic.insertOne(
{
        title:'MongoDB學(xué)習(xí)指南',
        tags:['java','程序設(shè)計','數(shù)據(jù)庫','NOSql'],
        content:'MongoDB學(xué)習(xí)之道,MongoDB是一個流行的NoSql數(shù)據(jù)庫',
        comments:[
            {
               user:'jake',
               content:'Good Text'
            },
            {
               user:'Leon',
               content:'Ok!!'
            }
        ],
        author:{
            name:'konghao',
            mail:'ynkonghao@gmail.com'
        }
    }
);

以上這條語句插入了一個topic的collection,在這個collection插入了一個文檔,這個文檔中有title,tags,conent,comments和author幾個信息,我們可以發(fā)現(xiàn),對應(yīng)MongoDB而言,除了添加基本數(shù)據(jù)類型之外還可以添加數(shù)組類型,tags數(shù)據(jù)就是一個數(shù)組類型,里面有四個值,而且author是一個對象類型。comments是一個數(shù)組,數(shù)組中的值又是一個對象。這就是MongoDB(文檔型數(shù)據(jù)庫)的特點,如果使用的是關(guān)系型數(shù)據(jù)庫,需要在數(shù)據(jù)庫中加入多張表來進(jìn)行存儲,之后通過join來查詢,而使用文檔型的數(shù)據(jù)庫對于這種結(jié)構(gòu)優(yōu)勢明顯,數(shù)據(jù)庫的設(shè)計問題將會在后面的章節(jié)詳細(xì)探討。

此時可以通過.來導(dǎo)航查詢想要的信息

>db.topic.find({"author.name":"konghao"}) ##查詢作者的名字叫konghao的用戶
{
        "_id" : ObjectId("5a12e8cb15ec7ee2ec16bbbb"),
        "title" : "MongoDB學(xué)習(xí)指南",
        "tags" : [
                "java",
                "程序設(shè)計",
                "數(shù)據(jù)庫",
                "NOSql"
        ],
        "content" : "MongoDB學(xué)習(xí)之道,MongoDB是一個流行的NoSql數(shù)據(jù)庫",
        "comments" : [
                {
                        "user" : "jake",
                        "content" : "Good Text"
                },
                {
                        "user" : "Leon",
                        "content" : "Ok!!"
                }
        ],
        "author" : {
                "name" : "konghao",
                "mail" : "ynkonghao@gmail.com"
        }
}

我們還可以利用javascript shell來查詢對象中的數(shù)組信息

> var d = db.topic.findOne(); ##將topic中的第一個條數(shù)據(jù)存儲到變量d中
> d.comments.length ##獲取comments的長度
2
> d.comments ##顯示所有的comments
[
        {
                "user" : "jake",
                "content" : "Good Text"
        },
        {
                "user" : "Leon",
                "content" : "Ok!!"
        }
]

MongoDB的批量插入操作

首先是關(guān)于主鍵的操作,如果添加數(shù)據(jù)的時候不添加任何主鍵,會自動生成一個主鍵,這個主鍵并不像關(guān)系數(shù)據(jù)庫中的自動遞增(這是為了分布式考慮的),而是使用"時間戳+機(jī)器編號+進(jìn)程編號+序列號"來生成的,這就可以保證每個id都是唯一的。id為5a0ed29a0149542ec19d4dd8,可以這樣分解:5a0ed29a 014954 2ec1 9d4dd8 首先前四個字節(jié)(5a0ed29a)表示時間戳,014954表示機(jī)器號,2ec1表示進(jìn)程編號,最后的9d4dd8表示序列號。

我們也可以手動指定id,使用_id來指定

> db.user.insertOne({_id:"user001",name:"foobar"}) ##插入一條數(shù)據(jù),id是自己指定
{ "acknowledged" : true, "insertedId" : "user001" }
> db.user.find({name:"foobar"}) ##找到id為自己指定的數(shù)據(jù)
{ "_id" : "user001", "name" : "foobar" }

通過db.collection.insertMany()可以批量插入數(shù)據(jù),MongoDB的批量插入效率比較高,它執(zhí)行的操作非常少,它首先會查詢批量插入的文檔中是否包含有 _ id, 如果包含有,就驗證_id是否已經(jīng)存在,當(dāng)發(fā)現(xiàn)所有的id都不存在,就直接存儲,此時不會做其他的檢查,這樣就保證了插入的效率,當(dāng)然這種也會存在一些問題,就是可能存在不合理的空數(shù)據(jù)存在。

db.user.insertMany([
...     {username:"foo",nickname:"FOO",password:"123"},
...     {username:"bar",nickname:"BAR",password:"111"},
...     {username:"hello",nickname:"HELLO",password:"123"}
... ]
... )
{
        "acknowledged" : true,
        "insertedIds" : [
                ObjectId("5a12fcf46533217cfe3d4090"),
                ObjectId("5a12fcf46533217cfe3d4091"),
                ObjectId("5a12fcf46533217cfe3d4092")
        ]
}

MongoDB的刪除操作

刪除操作上一講已經(jīng)介紹了,在3.2版本之前可以通過db.colletion.remove()來刪除,在3.2之后提供db.collection.deleteOne()來刪除一個數(shù)據(jù)和db.collection.deleteMany()來完成批量刪除。在新版本中要進(jìn)行刪除操作,需要加入過濾條件,如果要刪除所有的信息使用db.collection.deleteMany({})即可刪除所有數(shù)據(jù)。

> db.user.deleteMany({}) ##刪除所有數(shù)據(jù)
{ "acknowledged" : true, "deletedCount" : 6 }
> db.user.find()
>

接下來我們看一下批量刪除的速度問題,首先批量添加一些數(shù)據(jù)

for(int i=0;i<1000000;i++) {
  Document d = new Document("name", "foo")
  .append("no", i);
  collection.insertOne(d);
}

以上代碼通過java代碼插入了一百萬條數(shù)據(jù),之后我們來進(jìn)行一下刪除操作,使用deleteMany({})來刪除

> use demo
switched to db demo
> db.user.count()
1000000
> db.user.deleteMany({})
{ "acknowledged" : true, "deletedCount" : 1000000 }
>

100W條數(shù)據(jù)花去了18.35秒的時間,這個操作并不會刪除索引,所以時間較長,可以使用db.collection.drop()來完全刪除collection,效率就會高很多,這樣就需要重新創(chuàng)建索引,而且不能使用其他條件。

> db.user.count()
1000000
> db.user.drop()
true

這次刪除僅僅花了0.1秒的時間,所以如果希望全部刪除一個collection,使用drop顯然效率要高得多。

MongoDB的更新操作

MongoDB的更新方式比較多,首先介紹基于shell的方式,這種方式是把數(shù)據(jù)先讀出來,之后以對象的方式完成修改。這種方式一般用在修改較大的情況下。

db.user.insertOne({
    name:"foo",
    nickname:"bar",
    friends:12,
    enemies:2
})

我們希望修改為

db.user.insertOne({
    username:"foo",
    nickname:"bar",
    relations:{
        friends:12,
        enemies:2
    }
})

這種使用shell的替換方式比較方便

var u = db.user.findOne({name:"foo"}) ##查詢對象存儲到u中
u.relations = {friends:u.friends,enemies:u.enemies} ##設(shè)置relations的值
u.username = u.name ##修改name為username
delete u.friends ##刪除friends
delete u.enemies ##刪除enemies
delete u.name ##刪除name
db.user.update({name:"foo"},u) ##替換對象
db.user.findOne({username:"foo"}) ##查詢看看結(jié)果
{
        "_id" : ObjectId("5a155e7156d8db3756cafce3"),
        "nickname" : "bar",
        "relations" : {
                "friends" : 12,
                "enemies" : 2
        },
        "username" : "foo"
}

這就是替換的方式,它是基于一種編程的思想來進(jìn)行,使用這種方式要進(jìn)行單個對象的復(fù)雜修改比較適用。

接著介紹基于修改器的方法,這種方式使用比較廣泛,它的主要思路就是通過$符號來進(jìn)行修改這些操作,首先使用inc可以對數(shù)據(jù)進(jìn)行增加和減少,這個操作只能針對數(shù)字類型,小數(shù)或者整數(shù)

> db.topic.insertOne({title:"first",visites:107})##添加一條數(shù)據(jù)
> db.topic.update({title:"first"},{$inc:{visites:1}})##修改參觀次數(shù)增加1
WriteResult({ "nMatched" : 1, "nUpserted" : 0, "nModified" : 1 })
> db.topic.findOne({title:"first"})##查詢
{
        "_id" : ObjectId("5a15608556d8db3756cafce5"),
        "title" : "first",
        "visites" : 108
}
> db.topic.update({title:"first"},{$inc:{visites:-3}})##將數(shù)字修改了減少3
WriteResult({ "nMatched" : 1, "nUpserted" : 0, "nModified" : 1 })
> db.topic.findOne({title:"first"})
{
        "_id" : ObjectId("5a15608556d8db3756cafce5"),
        "title" : "first",
        "visites" : 105
}
>

使用set可以完成特定需求的修改,這是非常常用的一種方式

> db.author.findOne()##原數(shù)據(jù)
{
        "_id" : ObjectId("5a162f8956d8db3756cafce7"),
        "name" : "foo",
        "age" : 20,
        "gender" : "male",
        "intro" : "student"
}
> db.author.update({name:"foo"},{$set:{intro:"teacher"}})##使用$set進(jìn)行修改
WriteResult({ "nMatched" : 1, "nUpserted" : 0, "nModified" : 1 })

set不僅僅可以修改變量,還可以修改數(shù)據(jù)類型,下面將intro修改為數(shù)組類型

 db.author.update({name:"foo"},{$set:{intro:["teacher","programmer"]}})
WriteResult({ "nMatched" : 1, "nUpserted" : 0, "nModified" : 1 })
> db.author.findOne()
{
        "_id" : ObjectId("5a162f8956d8db3756cafce7"),
        "name" : "foo",
        "age" : 20,
        "gender" : "male",
        "intro" : [
                "teacher",
                "programmer"
        ]
}

使用unset可以刪除一個鍵,由于操作的時候都要通過寫鍵值對,所以,可以在要刪除的字段后隨便寫個值即可,一下代碼寫了一個1

> db.author.update({name:"foo"},{$unset:{intro:1}})##刪除intro屬性
WriteResult({ "nMatched" : 1, "nUpserted" : 0, "nModified" : 1 })
> db.author.findOne()
{
        "_id" : ObjectId("5a162f8956d8db3756cafce7"),
        "name" : "foo",
        "age" : 20,
        "gender" : "male"
}

使用set還可以修改關(guān)聯(lián)類型的值

> db.topic.findOne() ##修改前的數(shù)據(jù)
{
        "_id" : ObjectId("5a12e8cb15ec7ee2ec16bbbb"),
        "title" : "MongoDB學(xué)習(xí)指南",
        "content" : "MongoDB學(xué)習(xí)之道,MongoDB是一個流行的NoSql數(shù)據(jù)庫",
        "author" : {
                "name" : "konghao",
                "mail" : "ynkonghao@gmail.com"
        }
}
> db.topic.update(
    {"author.name":"konghao"},
    {$set:{"author.mail":"konghao@gmail.com"}}
)##修改關(guān)聯(lián)類型author
WriteResult({ "nMatched" : 1, "nUpserted" : 0, "nModified" : 1 })
> db.topic.findOne()##修改后的數(shù)據(jù)
{
        "_id" : ObjectId("5a12e8cb15ec7ee2ec16bbbb"),
        "title" : "MongoDB學(xué)習(xí)指南",
        "content" : "MongoDB學(xué)習(xí)之道,MongoDB是一個流行的NoSql數(shù)據(jù)庫",
        "author" : {
                "name" : "konghao",
                "mail" : "konghao@gmail.com"
        }
}

更新操作除了針對基本數(shù)據(jù)外還可以更新數(shù)組信息,使用push可以完成數(shù)組的插入,會在最后一條插入,如果沒有這個key,會自動創(chuàng)建一條數(shù)據(jù)插入

db.posts.findOne()
{
        "_id" : ObjectId("5a1656e656d8db3756cafce8"),
        "title" : "a blog",
        "content" : "...",
        "author" : "foo"
}
> db.posts.update({title:"a blog"},
    {$push:{comments:{name:"leon",email:"leon.email.com",content:"leon replay"}}})
    ##插入了一條comments的回復(fù),此時沒有comments。所以創(chuàng)建了一個數(shù)組類型的key出來
WriteResult({ "nMatched" : 1, "nUpserted" : 0, "nModified" : 1 })
> db.posts.findOne()##查詢結(jié)果
{
        "_id" : ObjectId("5a1656e656d8db3756cafce8"),
        "title" : "a blog",
        "content" : "...",
        "author" : "foo",
        "comments" : [
                {
                        "name" : "leon",
                        "email" : "leon.email.com",
                        "content" : "leon replay"
                }
        ]
}

使用addToSet可以向一個數(shù)組中添加元素,但這個有一個限定條件就是,如果已經(jīng)存在就不添加,這是非常好用的方法。

> db.users.findOne() ##原始數(shù)據(jù)
{
        "_id" : ObjectId("5a1659a756d8db3756cafce9"),
        "name" : "foo",
        "age" : 12,
        "email" : [
                "foo@example.com",
                "foo@163.com"
        ]
}
> db.users.update({name:"foo"},{$addToSet:{email:"foo@qq.com"}})##添加了一個email信息
WriteResult({ "nMatched" : 1, "nUpserted" : 0, "nModified" : 1 })
> db.users.update({name:"foo"},{$addToSet:{email:"foo@gmail.com"}})
WriteResult({ "nMatched" : 1, "nUpserted" : 0, "nModified" : 1 })
> db.users.findOne()##添加完成之后多了兩條email的數(shù)據(jù)
{
        "_id" : ObjectId("5a1659a756d8db3756cafce9"),
        "name" : "foo",
        "age" : 12,
        "email" : [
                "foo@example.com",
                "foo@163.com",
                "foo@qq.com",
                "foo@gmail.com"
        ]
}
> db.users.update({name:"foo"},{$addToSet:{email:"foo@gmail.com"}})##繼續(xù)添加一條已經(jīng)存在的信息
WriteResult({ "nMatched" : 1, "nUpserted" : 0, "nModified" : 0 })
> db.users.findOne()##并沒有添加成功
{
        "_id" : ObjectId("5a1659a756d8db3756cafce9"),
        "name" : "foo",
        "age" : 12,
        "email" : [
                "foo@example.com",
                "foo@163.com",
                "foo@qq.com",
                "foo@gmail.com"
        ]
}
>

使用each可以添加一個數(shù)組,此時如果配合addToSet會非常方便,此時只會添加不存在的數(shù)據(jù),以上一例的數(shù)據(jù)為藍(lán)本

db.users.update(
    {name:"foo"},
    {$addToSet:{email:{$each:["foo@qq.com","foo@sohu.com"]}}}
)##在email中的添加qq和sohu的郵箱,由于qq已經(jīng)存在,所以不會添加,只會添加sohu的郵箱
WriteResult({ "nMatched" : 1, "nUpserted" : 0, "nModified" : 1 })
> db.users.findOne()
{
        "_id" : ObjectId("5a165d5f56d8db3756cafcea"),
        "name" : "foo",
        "age" : 12,
        "email" : [
                "foo@example.com",
                "foo@163.com",
                "foo@gmail.com",
                "foo@qq.com",
                "foo@sohu.com"
        ]
}

通過pop可以刪除數(shù)組中的元素,可以指定從哪一個位置刪除。'{$pop:{xxx:1}}'表示刪除xx這個數(shù)組的最后一個元素,使用xxx:-1表示刪除第一個元素。

> db.users.findOne()##原始數(shù)據(jù)
{
        "_id" : ObjectId("5a165d5f56d8db3756cafcea"),
        "email" : [
                "foo@example.com",
                "foo@163.com",
                "foo@gmail.com",
                "foo@qq.com",
                "foo@sohu.com"
        ]
}
> db.users.update({name:"foo"},{$pop:{email:1}})##刪除email最后一個位置元素,sohu會被刪除
WriteResult({ "nMatched" : 1, "nUpserted" : 0, "nModified" : 1 })
> db.users.findOne()
{
        "_id" : ObjectId("5a165d5f56d8db3756cafcea"),
        "email" : [
                "foo@example.com",
                "foo@163.com",
                "foo@gmail.com",
                "foo@qq.com"
        ]
}
>db.users.update({name:"foo"},{$pop:{email:-1}})#使用-1刪除第一個元素
WriteResult({ "nMatched" : 1, "nUpserted" : 0, "nModified" : 1 })
> db.users.findOne()
{
        "_id" : ObjectId("5a165d5f56d8db3756cafcea"),
        "name" : "foo",
        "age" : 12,
        "email" : [
                "foo@163.com",
                "foo@gmail.com",
                "foo@qq.com"
        ]
}

對于update方法而言,第一個參數(shù)是條件,第二個參數(shù)是具體的修改信息,它還有第三個參數(shù),是一個布爾類型的值,如果為true,表示修改操作,如果沒有找到條件,會自動創(chuàng)建需要修改的數(shù)據(jù),默認(rèn)是false。先看看如下實例

> db.statistics.update({url:"/blog"},{$inc:{visites:1}}) ##增加/blog地址的訪問次數(shù)
WriteResult({ "nMatched" : 0, "nUpserted" : 0, "nModified" : 0 }) ##由于沒有數(shù)據(jù),所以不會更新
> db.statistics.findOne()##沒有更新
null
> db.statistics.update({url:"/blog"},{$inc:{visites:1}},true)##設(shè)置第三個參數(shù)為true,會自動添加
WriteResult({
        "nMatched" : 0,
        "nUpserted" : 1,
        "nModified" : 0,
        "_id" : ObjectId("5a16ea57d76cb43bc7d5de15")
})
> db.statistics.findOne()##添加了一條數(shù)據(jù)
{
        "_id" : ObjectId("5a16ea57d76cb43bc7d5de15"),
        "url" : "/blog",
        "visites" : 1
}

已上操作非常方便,因為這個操作是原子性的,如果我們使用shell來更新,需要先取出對象,之后判斷,如果存在執(zhí)行更新,不存在執(zhí)行添加,這個操作的步驟較多而且不是原子性。不僅效率不高,而且如果更新到某一個部分拋出異常就會出現(xiàn)數(shù)據(jù)不統(tǒng)一操作。

接下來介紹save這個shell腳本,也非常的實用,它會檢查數(shù)據(jù)是否存在,如果存在就更新,如果不存在就插入,這也是比較實用的操作。

> db.users.findOne()##原始數(shù)據(jù)
{
        "_id" : ObjectId("5a165d5f56d8db3756cafcea"),
        "name" : "foo",
        "age" : 33,
        "email" : [
                "foo@163.com",
                "foo@gmail.com",
                "foo@qq.com"
        ]
}
> var foo=db.users.findOne({name:"foo"})
> foo.age = 44##修改age為44,age存在會執(zhí)行修改
44
> foo.num = 33##num不存在會自動添加這個屬性
33
> db.users.save(foo)##執(zhí)行save方法
WriteResult({ "nMatched" : 1, "nUpserted" : 0, "nModified" : 1 })
> db.users.find({name:"foo"}).pretty()
{
        "_id" : ObjectId("5a165d5f56d8db3756cafcea"),
        "name" : "foo",
        "age" : 44,##修改的變量
        "email" : [
                "foo@163.com",
                "foo@gmail.com",
                "foo@qq.com"
        ],
        "num" : 33 ##新添加的
}
>

最后來實際測試一下更新的效率問題,這里將會測試inc,set和push三個方法

long start = new Date().getTime();
for(int i=0;i<100000;i++) {
  collection.updateOne(gte("num",1),inc("num",1));//使用java完成更新
}
long end = new Date().getTime();
System.out.println((end-start)/1000);

插入了10W條數(shù)據(jù),最后的時間是13秒左右,差不多每秒7000多條,對于一個臺個人電腦而言效率還算不錯,接著測試set操作。

long start = new Date().getTime();
for(int i=0;i<100000;i++) {
  collection.updateOne(eq("name","leon"),set("num",i));//使用java完成修改
}
long end = new Date().getTime();
System.out.println((end-start)/1000);

同樣操作10W條數(shù)據(jù),我們發(fā)現(xiàn)時間16秒左右,稍微慢一點點,這說明只要在文檔結(jié)構(gòu)不做調(diào)整的情況下,set的速度也是非常理想的。最后來測試push操作,push僅僅操作1W條數(shù)數(shù)據(jù)

long start = new Date().getTime();
for(int i=0;i<10000;i++) {
  collection.updateOne(eq("name","Ada"),push("nums",i));
}
long end = new Date().getTime();
System.out.println((end-start)/1000);

時間約為25秒左右,我們發(fā)現(xiàn),push操作的效率比較的低,所以需要注意push的瓶頸問題,必要時可以將數(shù)組拿出來放到一個單獨的集合中。

這一部分詳細(xì)的探討了MongoDB的寫操作,并且介紹了多數(shù)非常常用的方法,下一部分將會詳細(xì)的探討查詢相關(guān)的操作。

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

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

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