MongoDB使用中問題匯總

如何集成MongoDB驅(qū)動(dòng)包

推薦使用Maven管理包依賴關(guān)系:

<dependency>
  <groupId>org.mongodb</groupId>
  <artifactId>mongo-java-driver</artifactId>
  <version>2.13.2</version>
</dependency>

如何連接

主要有兩種連接方式

  • 單機(jī)直連
  • Replica Set連接,自動(dòng)發(fā)現(xiàn)Primary主機(jī),在多集群情況下強(qiáng)烈推薦使用這種連接方式

示例代碼

    //單機(jī)直連
    MongoClient mongoClient = new MongoClient( "localhost" , 27017 );
    
    //Replica Set連接
    MongoClientOptions options = MongoClientOptions.builder().autoConnectRetry(true).connectTimeout(60000).build();
    MongoCredential credential = MongoCredential.createMongoCRCredential("username", "dbname", "password".toCharArray());
    MongoClient mongoClient = new MongoClient(
                Arrays.asList(
                        new ServerAddress("mongoserver1", 34001),
                        new ServerAddress("mongoserver2", 34001),
                        new ServerAddress("mongoserver3", 34001)
                ), Arrays.asList(credential), options);

    //Replica Set連接 uri寫法
    String connectionString = "mongodb://username:password@mongoserver1:34001,mongoserver2:34001,mongoserver3:34001/dbname?AutoConnectRetry=true";
    MongoClientURI mongoClientURI = new MongoClientURI(connectionString);
    MongoClient mongoClient = new MongoClient(mongoClientURI);

    //spring boot 請(qǐng)?jiān)趐roperties里邊使用uri方式進(jìn)行連接
    spring.data.mongodb.uri=mongodb://username:password@mongoserver1:34001,mongoserver2:34001,mongoserver3:34001/dbname?AutoConnectRetry=true
    spring.data.mongodb.repositories.enabled=true

MongoDB Update的正確用法

通常一個(gè)文檔只會(huì)有一小部分需要更新,如果我們把新文檔做為update方法的參數(shù)顯得很啰嗦很麻煩,特別是文檔比較復(fù)雜的時(shí)候。而利用原子的更新修改器可以使得這種部分的更新極為方便高效。
更新修改器是種特殊的鍵,用來指定復(fù)雜的更新操作,比如調(diào)整,增加或者刪除鍵,還可能是操作數(shù)組或者內(nèi)嵌文檔
$set用來指定一個(gè)鍵的值.如果這個(gè)鍵存在,就修改它;不存在,就創(chuàng)建它。

> db.name.find()
{ "_id" : ObjectId("505a5925f67c1b9a341caefb"), "fname" : "jeff", "lname" : "jiang" }
> db.name.update({"_id" : ObjectId("505a5925f67c1b9a341caefb")},{$set:{"fname" : "jeffery"}})
> db.name.find()
{ "_id" : ObjectId("505a5925f67c1b9a341caefb"), "fname" : "jeffery", "lname" : "jiang" }
# 可以看到,原文檔的"fname"是存在的,所以$set修改器只修改了它的值("jeff"-->"jeffery")
> db.name.update({"_id" : ObjectId("505a5925f67c1b9a341caefb")},{$set:{age:23}})
> db.name.find()
{ "_id" : ObjectId("505a5925f67c1b9a341caefb"), "age" : 23, "fname" : "jeffery", "lname" : "jiang" }

示例代碼

db.getCollection("restaurants").updateOne(new Document("name", "Juni"), new Document("$set", new Document("cuisine", "American (New)")) .append("$currentDate", new Document("lastModified", true)))

如何開啟讀寫分離

默認(rèn)情況下驅(qū)動(dòng)是從Replica Set 集群中的 Primary 上進(jìn)行讀寫的,應(yīng)用可以在讀多寫少的場(chǎng)景下開啟讀寫分離,提高效率。

這里介紹下 readPreference 這個(gè)參數(shù):###

  • primary
    主節(jié)點(diǎn),默認(rèn)模式,讀操作只在主節(jié)點(diǎn),如果主節(jié)點(diǎn)不可用,報(bào)錯(cuò)或者拋出異常。
  • primaryPreferred
    首選主節(jié)點(diǎn),大多情況下讀操作在主節(jié)點(diǎn),如果主節(jié)點(diǎn)不可用,如故障轉(zhuǎn)移,讀操作在從節(jié)點(diǎn)。
  • secondary
    從節(jié)點(diǎn),讀操作只在從節(jié)點(diǎn), 如果從節(jié)點(diǎn)不可用,報(bào)錯(cuò)或者拋出異常。
  • secondaryPreferred
    首選從節(jié)點(diǎn),大多情況下讀操作在從節(jié)點(diǎn),特殊情況(如單主節(jié)點(diǎn)架構(gòu))讀操作在主節(jié)點(diǎn)。
  • nearest
    最鄰近節(jié)點(diǎn),讀操作在最鄰近的成員,可能是主節(jié)點(diǎn)或者從節(jié)點(diǎn)

示例代碼

//uri寫法
mongodb://username:password@mongoserver1:34001,mongoserver2:34001,mongoserver3:34001/dbname?AutoConnectRetry=true&readPreference=secondaryPreferred

//java寫法
MongoClientOptions options = MongoClientOptions.builder().readPreference(ReadPreference.secondaryPreferred()).build();
MongoClient mongoClient = new MongoClient(
                Arrays.asList(
                        new ServerAddress("mongoserver1", 34001),
                        new ServerAddress("mongoserver2", 34001),
                        new ServerAddress("mongoserver3", 34001)
                ), Arrays.asList(credential), options);

在Mongodb中最多能創(chuàng)建多少集合?

默認(rèn)情況下,MongoDB 的每個(gè)數(shù)據(jù)庫的命名空間保存在一個(gè) 16MB 的 .ns 文件中,平均每個(gè)命名占用約 628 字節(jié),也即整個(gè)數(shù)據(jù)庫的命名空間的上限約為 24000。
每一個(gè)集合、索引都將占用一個(gè)命名空間。所以,如果每個(gè)集合有一個(gè)索引(比如默認(rèn)的 _id 索引),那么最多可以創(chuàng)建 12000 個(gè)集合。如果索引數(shù)更多,則可創(chuàng)建的集合數(shù)就更少了。同時(shí),如果集合數(shù)太多,一些操作也會(huì)變慢。甚至使得MongoDB集群無法服務(wù)的情況發(fā)生!

MongoDB有傳統(tǒng)數(shù)據(jù)庫的事務(wù)和事務(wù)回滾么?

沒有,請(qǐng)不要把它當(dāng)成關(guān)系型數(shù)據(jù)庫來使用,對(duì)于MongoDB集群來說,默認(rèn)情況下數(shù)據(jù)也不是強(qiáng)一致性的,而是最終一致性。如果對(duì)數(shù)據(jù)一致性比較敏感建議更改WriteConcern級(jí)別,但后果是降低了性能,請(qǐng)酌情考慮。

MongoDB有命名規(guī)范么?

  • 不能是空字符串
  • 不能含有.、''、*、/、\、<、>、:、?、$、\0。建議只使用ASCII碼中字母和數(shù)字
  • 數(shù)據(jù)庫名區(qū)分大小寫
  • 數(shù)據(jù)庫名長(zhǎng)度最多為64字節(jié)
  • 集合名不能包含\0字符,這個(gè)字符表示集合名的結(jié)束
  • 集合名不能是空字符串""
  • 集合名不能使用系統(tǒng)集合的保留前綴"system."
  • 集名名中不建議包含字符'$',雖然很多驅(qū)動(dòng)程序可以支持包含此字符的集合名

MongoDB有系統(tǒng)保留庫名么?

  • admin
  • local
  • config

MongoDB有連接池么?

MongoDB驅(qū)動(dòng)中其實(shí)已經(jīng)是一個(gè)現(xiàn)成的連接池了,而且線程安全。這個(gè)內(nèi)置的連接池默認(rèn)初始了100個(gè)連接,每一個(gè)操作(增刪改查等)都會(huì)獲取一個(gè)連接,執(zhí)行操作后釋放連接。
【題外話】請(qǐng)務(wù)必記得關(guān)閉資源,并且設(shè)置合理的池子連接數(shù)和超時(shí)時(shí)間。

內(nèi)置連接池有多個(gè)重要參數(shù),分別是:###

  • connectionsPerHost:每個(gè)主機(jī)答應(yīng)的連接數(shù)(每個(gè)主機(jī)的連接池大?。?dāng)連接池被用光時(shí),會(huì)被阻塞住,默認(rèn)值為100
  • threadsAllowedToBlockForConnectionMultiplier:線程隊(duì)列數(shù),它和上面connectionsPerHost值相乘的結(jié)果就是線程隊(duì)列最大值。如果連接線程排滿了隊(duì)列就會(huì)拋出“Out of semaphores to get db”錯(cuò)誤,默認(rèn)值為5,則最多有500個(gè)線程可以等待獲取連接
  • maxWaitTime: 被阻塞線程從連接池獲取連接的最長(zhǎng)等待時(shí)間(ms)。默認(rèn)值為120,000
  • connectTimeout:在建立(打開)套接字連接時(shí)的超時(shí)時(shí)間(ms)。默認(rèn)值為10,000
  • socketTimeout:套接字超時(shí)時(shí)間(ms)。默認(rèn)值為0,無限制(infinite)
  • autoConnectRetry:這個(gè)控制是否在連接時(shí),會(huì)自動(dòng)重試,2.13驅(qū)動(dòng)已經(jīng)【廢棄】,請(qǐng)使用connectTimeout代替它

連接池的MaximumPoolSize要有個(gè)合理值,否則這個(gè)值數(shù)據(jù)量的連接都被占用,后面再有新的連接創(chuàng)建時(shí)就要等待了,而不能超出池上限新建連接。除此之外還要設(shè)置合理的連接等待,連接超時(shí)時(shí)間,以防止一個(gè)連接占用時(shí)間過長(zhǎng),影響其它連接請(qǐng)求。

connectTimeout 和 socketTimeout 的區(qū)別:###

一次完整的請(qǐng)求包括三個(gè)階段:

  • 建立連接
  • 數(shù)據(jù)傳輸
  • 斷開連接

如果與服務(wù)器(這里指數(shù)據(jù)庫)請(qǐng)求建立連接的時(shí)間超過ConnectTimeout,就會(huì)拋 ConnectionTimeOutException,即服務(wù)器連接超時(shí),沒有在規(guī)定的時(shí)間內(nèi)建立連接。
如果與服務(wù)器連接成功,就開始數(shù)據(jù)傳輸了。
如果服務(wù)器處理數(shù)據(jù)用時(shí)過長(zhǎng),超過了SocketTimeOut,就會(huì)拋出SocketTimeOutExceptin,即服務(wù)器響應(yīng)超時(shí),服務(wù)器沒有在規(guī)定的時(shí)間內(nèi)返回給客戶端數(shù)據(jù)。

所以這該死的超時(shí)該怎么配?

這里有一份國(guó)外寫的關(guān)于超時(shí)的建議:
http://blog.mongolab.com/2013/10/do-you-want-a-timeout/
上文給出的通常情況下:connectTimeout=5000,socketTimeout=0

附錄

http://api.mongodb.org/java/2.13/com/mongodb/MongoClientOptions.Builder.html
http://api.mongodb.org/java/2.13/com/mongodb/MongoClientURI.html

關(guān)于WriteConcern

MongoDB提供了一個(gè)配置參數(shù):write concern 來讓用戶自己衡量性能和寫安全。分布式數(shù)據(jù)庫中這樣的參數(shù)比較常見,記得Cassandra中也有一個(gè)類似參數(shù),不過那個(gè)好像是要寫入幾個(gè)節(jié)點(diǎn)返回成功。其實(shí)道理都一樣分布式的集群環(huán)境考慮到性能因素不能確保每個(gè)成員都寫入后在返回成功,所以只能交給用戶根據(jù)實(shí)際場(chǎng)景衡量。

  • Unacknowledged
    這個(gè)級(jí)別也屬于比較低的級(jí)別,以前這個(gè)級(jí)別是驅(qū)動(dòng)配置的默認(rèn)級(jí)別,不過后來調(diào)整成Acknowledged級(jí)別。在這個(gè)級(jí)別下,這個(gè)驅(qū)動(dòng)會(huì)根據(jù)當(dāng)前系統(tǒng)的網(wǎng)絡(luò)配置進(jìn)行網(wǎng)絡(luò)問題的檢測(cè),不等待Mongd的返回。代碼測(cè)試:本地網(wǎng)絡(luò)問題是否有異常?本地網(wǎng)絡(luò)無問題是遠(yuǎn)程server問題是否異常?
  • Acknowledged
    這個(gè)級(jí)別算是中等級(jí)別的配置,這個(gè)級(jí)別能夠拿到mongod的返回信息:dupkey Error,以及一些其他的問題。現(xiàn)在這個(gè)級(jí)別是驅(qū)動(dòng)的默認(rèn)級(jí)別,估計(jì)是10gen公司發(fā)現(xiàn)好多人評(píng)價(jià)Mongodb不靠譜后改的。一般系統(tǒng)這個(gè)級(jí)別也就夠用了。由于默認(rèn)級(jí)別是Acknowledged,內(nèi)部用getLastError方法檢查是否寫入成功的時(shí)候是也不用設(shè)置任何參數(shù),對(duì)與Replset來說可以在配置中進(jìn)行g(shù)etLastErrorDefaults的配置,如果沒有的話默認(rèn)則是Master收到就ok。
  • Journaled
    等到操作記錄到Journal Log中才返回操作結(jié)果,也就是下一次JournaledLog提交。這種情況可以容忍服務(wù)器突然宕機(jī),斷電等意外的恢復(fù)。出去上邊的配置還要在啟動(dòng)mongod的時(shí)候加上journaling 參數(shù)確??梢允褂谩ommitlog提交間隔時(shí)間是可以配置的,單磁盤設(shè)備(physical volume, RAID device, or LVM volume)每100ms提交一次,和數(shù)據(jù)文件刷出相同頻率,日志和數(shù)據(jù)分開磁盤設(shè)備的30ms提交一次。在插入數(shù)據(jù)是如果使用{j:true}則會(huì)縮短到已配置的默認(rèn)設(shè)置1/3的時(shí)間。
  • Replica Acknowledged
    在副本集中如果w設(shè)置為2的話則至少已經(jīng)吸入到一個(gè)secondary中,我猜測(cè)寫入secondary這個(gè)級(jí)別是Acknowledged級(jí)別,majority是多個(gè)secondary已經(jīng)寫入。如果手賤設(shè)置w參數(shù)大于replset中需要復(fù)制的secondarys的話,操作就一直等待直到達(dá)到已寫入數(shù)據(jù)的服務(wù)器數(shù)量符合要求,也可以設(shè)置timeout值來指明最長(zhǎng)等待時(shí)間。{ getLastError: 1, w: 2, wtimeout:5000 }

附錄

http://my.oschina.net/u/217548/blog/195995
http://docs.mongodb.org/manual/core/write-concern/

MongoDB的鎖機(jī)制

MongoDB的鎖機(jī)制和一般關(guān)系數(shù)據(jù)庫如 MySQL(InnoDB), Oracle 有很大的差異,InnoDB 和 Oracle 能提供行級(jí)粒度鎖,而 MongoDB v2 只能提供庫級(jí)粒度鎖,這意味著當(dāng) MongoDB 一個(gè)寫鎖處于占用狀態(tài)時(shí),其它的讀寫操作都得干等。

初看起來庫級(jí)鎖在大并發(fā)環(huán)境下有嚴(yán)重的問題,但是 MongoDB 依然能夠保持大并發(fā)量和高性能,這是因?yàn)?MongoDB 的鎖粒度雖然很粗放,但是在鎖處理機(jī)制和關(guān)系數(shù)據(jù)庫鎖有很大差異,主要表現(xiàn)在:

  • MongoDB 沒有完整事務(wù)支持,操作原子性只到單個(gè) document 級(jí)別,所以通常操作粒度比較?。?/li>
  • MongoDB 鎖實(shí)際占用時(shí)間是內(nèi)存數(shù)據(jù)計(jì)算和變更時(shí)間,通常很快;
  • MongoDB 鎖有一種臨時(shí)放棄機(jī)制,當(dāng)出現(xiàn)需要等待慢速 IO 讀寫數(shù)據(jù)時(shí),可以先臨時(shí)放棄,等 IO 完成之后再重新獲取鎖。

通常不出問題不等于沒有問題,如果數(shù)據(jù)操作不當(dāng),依然會(huì)導(dǎo)致長(zhǎng)時(shí)間占用寫鎖,比如下面提到的前臺(tái)建索引操作,當(dāng)出現(xiàn)這種情況的時(shí)候,整個(gè)數(shù)據(jù)庫就處于完全阻塞狀態(tài),無法進(jìn)行任何讀寫操作,情況十分嚴(yán)重。

解決問題的方法,盡量避免長(zhǎng)時(shí)間占用寫鎖操作,如果有一些集合操作實(shí)在難以避免,可以考慮把這個(gè)集合放到一個(gè)單獨(dú)的 MongoDB 庫里,因?yàn)?MongoDB 不同庫鎖是相互隔離的,分離集合可以避免某一個(gè)集合操作引發(fā)全局阻塞問題。

建索引導(dǎo)致數(shù)據(jù)庫阻塞

上面提到了 MongoDB 庫級(jí)鎖的問題,建索引就是一個(gè)容易引起長(zhǎng)時(shí)間寫鎖的問題,MongoDB 在前臺(tái)建索引時(shí)需要占用一個(gè)寫鎖(而且不會(huì)臨時(shí)放棄),如果集合的數(shù)據(jù)量很大,建索引通常要花比較長(zhǎng)時(shí)間,特別容易引起問題。

解決的方法很簡(jiǎn)單,MongoDB 提供了兩種建索引的訪問,一種是 background 方式,不需要長(zhǎng)時(shí)間占用寫鎖,另一種是非 background 方式,需要長(zhǎng)時(shí)間占用鎖。使用 background 方式就可以解決問題。
例如,為超大表 posts 建立索引

//千萬不用使用
db.posts.ensureIndex({user_id: 1})
//而應(yīng)該使用
db.posts.ensureIndex({user_id: 1}, {background: 1})

哪些操作會(huì)對(duì)數(shù)據(jù)庫產(chǎn)生鎖

操作 鎖類型
Issue a query Read lock
Get more data from a cursor Read lock
Insert data Write lock
Remove data Write lock
Update data Write lock
Map-reduce Read lock and write lock, unless operations are specified as non-atomic. Portions of map-reduce jobs can run concurrently.
Create an index Building an index in the foreground, which is the default, locks the database for extended periods of time.
db.eval() Write lock. db.eval() blocks all other JavaScript processes.
eval Write lock. If used with the nolock lock option, the eval option does not take a write lock and cannot write data to the database.
aggregate() Read lock

附錄

https://ruby-china.org/topics/20128
http://docs.mongodb.org/v2.6/faq/concurrency/
http://docs.mongodb.org/v2.6/reference/method/db.collection.ensureIndex/#db.collection.ensureIndex

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡(jiǎn)書系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。

推薦閱讀更多精彩內(nèi)容