MongoDB使用中問題匯總

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

推薦使用Maven管理包依賴關系:

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

如何連接

主要有兩種連接方式

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

示例代碼

    //單機直連
    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 請在properties里邊使用uri方式進行連接
    spring.data.mongodb.uri=mongodb://username:password@mongoserver1:34001,mongoserver2:34001,mongoserver3:34001/dbname?AutoConnectRetry=true
    spring.data.mongodb.repositories.enabled=true

MongoDB Update的正確用法

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

如何開啟讀寫分離

默認情況下驅(qū)動是從Replica Set 集群中的 Primary 上進行讀寫的,應用可以在讀多寫少的場景下開啟讀寫分離,提高效率。

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

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

示例代碼

//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)建多少集合?

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

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

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

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

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

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

  • admin
  • local
  • config

MongoDB有連接池么?

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

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

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

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

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

一次完整的請求包括三個階段:

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

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

所以這該死的超時該怎么配?

這里有一份國外寫的關于超時的建議:
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

關于WriteConcern

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

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

附錄

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

MongoDB的鎖機制

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

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

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

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

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

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

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

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

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

哪些操作會對數(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

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

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

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