本文包括以下幾個方面:
–?安全措施
– 部署架構
– 系統優化
– 索引設計
– 備份監控
– 模式設計
– 程序配置
一、安全措施
1.1 為MongoDB集群啟用認證鑒權
MongoDB服務器在默認安裝下不啟用鑒權。這意味著每個人都可以直接連接到mongod實例并執行任意數據庫操作。建議按照文檔啟用鑒權 http://docs.mongoing.com/manual-zh/tutorial/enable-authentication.html
1.2 為不同用戶分配不同的角色權限
MongoDB支持按角色定義的權限系統。你應該基于“最少權限”準則,顯式的為用戶分配僅需要的相應權限。
1.3 使用中央鑒權服務器
盡可能使用LDAP、Kerbero之類的中央鑒權服務器,并使用強口令策略。
1.4 為需要訪問MongoDB的應用服務器創建白名單(防火墻配置)
如果你的服務器有多個網卡,建議只在內網的IP上監聽服務。
1.5 對敏感數據使用加密引擎
MongoDB企業版支持存儲加密,對涉及到客戶的敏感數據應該使用加密引擎來保護數據。
二、部署架構
2.1 至少使用3個數據節點的復制集
MongoDB的建議最小部署是3個數據節點構成的復制集。復制集可以提供以下優點:
– 系統99.999% 高可用
– 自動故障切換
– 數據冗余
– 容災部署
– 讀寫分離
2.2 不用太早分片
分片可以用來擴展你系統的讀寫能力,但是分片也會帶來不少新的挑戰比如說管理上的復雜度,成本的增加,選擇合適片鍵的挑戰性等等。一般來說,你應該先窮盡了其他的性能調優的選項以后才開始考慮分片,比如說,索引優化,模式優化,代碼優化,硬件資源優化,IO優化等。
2.3 選擇合適的分片數
分片的一些觸發條件為:
數據總量太大,無法在一臺服務器上管理
并發量太高,一臺服務器無法及時處理
磁盤IO壓力太大
單機系統內存不夠大,無法裝下熱數據
服務器網卡處理能力達到瓶頸
多地部署情況下希望支持本地化讀寫
取決于你分片的觸發條件,你可以按照總的需求 然后除以每一臺服務器的能力來確定所需的分片數。
2.4 為每個分片部署足夠的復制集成員
分片之間的數據互相不復制。每個分片的數據必須在分片內保證高可用。因此,對每一個分片MongoDB要求至少部署3個數據節點來保證該分片在絕大部分時間都不會因為主節點宕機而造成數據不可用。
2.5 選擇合適的片鍵
在分片場景下, 最重要的一個考量是選擇合適的片鍵。選擇片鍵需要考慮到應用的讀寫模式。通常來說一個片鍵要么是對寫操作優化,要么是對讀操作優化。要根據哪種操作更加頻繁而進行相應的權衡。
片鍵值應該具有很高的基數,或者說,這個片鍵在集合內有很多不同的值,例如_id就是一個基數很高的片鍵因為_id值不會重復
片鍵一般不應該是持續增長的,比如說timestamp就是個持續增長的片鍵。此類片鍵容易造成熱分片現象,即新的寫入集中到某一個分片上
好的片鍵應該會讓查詢定向到某一個(或幾個)分片上從而提高查詢效率。一般來說這個意味著片鍵應該包括最常用查詢用到的字段
好的片鍵應該足夠分散,讓新的插入可以分布到多個分片上從而提高并發寫入率。
可以使用幾個字段的組合來組成片鍵,以達到幾個不同的目的(基數,分散性,及查詢定向等)
三、系統優化
3.1 使用SSD或RAID10來提高存儲IOPS能力
MongoDB是一個高性能高并發的數據庫,其大部分的IO操作為隨機更新。一般來說本機自帶的SSD是最佳的存儲方案。如果使用普通的硬盤,建議使用RAID10條帶化來提高IO通道的并發能力。
3.2 為Data和Journal/log使用單獨的物理卷
MongoDB很多的性能瓶頸和IO相關。建議為日志盤(Journal和系統日志)單獨設定一個物理卷,減少對數據盤IO的資源占用。
系統日志可以直接在命令行或者配置文件參數內指定。Journal日志不支持直接指定到另外的目錄,可以通過對Journal目錄創建symbol link的方式來解決。
3.3 使用XFS文件系統
MongoDB在WiredTiger存儲引擎下建議使用XFS文件系統。Ext4最為常見,但是由于ext文件系統的內部journal和WiredTiger有所沖突,所以在IO壓力較大情況下表現不佳。
3.4 WiredTiger下謹慎使用超大緩存
WiredTiger 對寫操作的落盤是異步發生的。默認是60秒做一次checkpoint。做checkpoint需要對內存內所有臟數據遍歷以便整理然后把這些數據寫入硬盤。如果緩存超大(如大于128G),那么這個checkpoint時間就需要較長時間。在checkpoint期間數據寫入性能會受到影響。目前建議實際緩存設置在64GB或以下。
3.5 關閉Transparent Huge Pages
Transparent Huge Pages (THP) 是Linux的一種內存管理優化手段,通過使用更大的內存頁來減少Translation Lookaside Buffer(TLB)的額外開銷。 MongoDB數據庫大部分是比較分散的小量數據讀寫,THP對MongoDB這種工況會有負面的影響所以建議關閉。
http://docs.mongoing.com/manual-zh/tutorial/transparent-huge-pages.html
3.6 啟用Log Rotation
防止MongoDB 的log文件無限增大,占用太多磁盤空間。好的實踐是啟用log rotation并及時清理歷史日志文件。
http://docs.mongoing.com/manual-zh/tutorial/rotate-log-files.html
3.7 分配足夠的Oplog空間
足夠的Oplog空間可以保證有足夠的時間讓你從頭恢復一個從節點,或者對從節點執行一些比較耗時的維護操作。假設你最長的下線維護操作需要H小時,那么你的Oplog 一般至少要保證可以保存 H?2 或者 H3 小時的oplog。
如果你的MongoDB部署的時候未設置正確的Oplog 大小,可以參照下述鏈接來調整:
http://docs.mongoing.com/manual-zh/tutorial/change-oplog-size.html
3.8 關閉數據庫文件的atime
我們建議在文件系統的mount參數上加上noatime,nobarrier兩個選項。用noatime mount的話,文件系統在程序訪問對應的文件或者文件夾時,不會更新對應的access time。
一般來說,Linux會給文件記錄了三個時間,change time, modify time和access time。我們可以通過stat來查看文件的三個時間。其中access time指文件最后一次被讀取的時間,modify time指的是文件的文本內容最后發生變化的時間,change time指的是文件的inode最后發生變化(比如位置、用戶屬性、組屬性等)的時間。
一般來說,文件都是讀多寫少,而且我們也很少關心某一個文件最近什么時間被訪問了。所以,我們建議采用noatime選項,這樣文件系統不記錄access time,避免浪費資源。禁止系統對文件的訪問時間更新會有效提高文件讀取的性能,這個可以通過在/etc/fstab文件中增加noatime參數來實現。例如:
/dev/xvdb /data ext4 noatime 0 0
修改完文件后重新mount就可以:
mount -o remount /data
3.9 提高默認文件描述符和進程/線程數限制
Linux默認的文件描述符數和最大進程數對于MongoDB來說一般會太低。建議把這個數值設為64000。因為MongoDB服務器對每一個數據庫文件以及每一個客戶端連接都需要用到一個文件描述符。如果這個數字太小的話在大規模并發操作情況下可能會出錯或無法響應。 你可以通過以下命令來修改這些值:
ulimit -n 64000
ulimit -u 64000
3.10 禁止NUMA
非一致存儲訪問結構 (NUMA : Non-Uniform Memory Access) 也是最新的內存管理技術。它和對稱多處理器結構 (SMP : Symmetric Multi-Processor) 是對應的。簡單的隊別如下:
如圖所示,詳細的NUMA信息我們這里不介紹了。
但是我們可以直觀的看到:SMP訪問內存的都是代價都是一樣的;但是在NUMA架構下,本地內存的訪問和非 本地內存的訪問代價是不一樣的。
對應的根據這個特性,操作系統上,我們可以設置進程的內存分配方式。目前支持的方式包括:
–interleave=nodes
–membind=nodes
–cpunodebind=cpus
–physcpubind=cpus
–localalloc
–preferred=node
簡而言之,就是說,你可以指定內存在本地分配,在某幾個CPU節點分配或者輪詢分配。
除非是設置為–interleave=nodes輪詢分配方式,即內存可以在任意NUMA節點上分配這種方式以外,其他的方式就算其他NUMA節點上還有內存剩余,Linux也不會把剩余的內存分配給這個進程,而是采用SWAP的方式來獲得內存。有經驗的系統管理員或者DBA都知道SWAP導致的數據庫性能下降有多么坑爹。
在一個使用NUMA技術的多處理器Linux系統上,你應該禁止NUMA的使用。MongoDB在NUMA環境下運行性能有時候會可能變慢,特別是在進程負載很高的情況下。
3.11 vm.swappiness
vm.swappiness是操作系統控制物理內存交換出去的策略。它允許的值是一個百分比的值,最小為0,最大運行100,該值默認為60。
vm.swappiness設置為0表示盡量少swap,100表示盡量將inactive的內存頁交換出去。
具體的說:當內存基本用滿的時候,系統會根據這個參數來判斷是把內存中很少用到的inactive內存交換出去,還是釋放數據的cache。cache中緩存著從磁盤讀出來的數據,根據程序的局部性原理,這些數據有可能在接下來又要被讀取;inactive內存顧名思義,就是那些被應用程序映射著,但是“長時間”不用的內存。
我們可以利用vmstat看到inactive的內存的數量,也可以通過/proc/meminfo 你可以看到更詳細的信息:
$ cat /proc/meminfo | grep -i inac
Inactive:????????7524180 kB
Inactive(anon):??1577932 kB
Inactive(file):??5946248 kB
這里我們對不活躍inactive內存進一步深入討論。
Linux中,內存可能處于三種狀態:free,active和inactive。
眾所周知,Linux Kernel在內部維護了很多LRU列表用來管理內存,系統內核會根據內存頁的訪問情況,不定時的將活躍active內存被移到inactive列表中,這些inactive的內存可以被交換到swap中去。
MongoDB本身也是一個內存使用量較大的數據庫,它占用的內存比較多,不經常訪問的內存也會不少,這些內存如果被Linux錯誤的交換出去了,將浪費很多CPU和IO資源。所以最好在MongoDB的服務器上設置vm.swappiness=0,盡可能少地使用swap。
一般來說,MySQL,特別是InnoDB管理內存緩存,它占用的內存比較多,不經常訪問的內存也會不少,這些內存如果被Linux錯誤的交換出去了,將浪費很多CPU和IO資源。 InnoDB自己管理緩存,cache的文件數據來說占用了內存,對InnoDB幾乎沒有任何好處。所以,我們在MySQL的服務器上最好設置vm.swappiness=0。
3.12 預讀值(readahead)設置
預讀值是文件操作系統的一個優化手段,大致就是在程序請求讀取一個頁面的時候,文件系統會同時讀取下面的幾個頁面并返回。這原因是因為很多時候IO最費時的磁盤尋道。通過預讀,系統可以提前把緊接著的數據同時返回。假設程序是在做一個連續讀的操作,那么這樣可以節省很多磁盤尋道時間。
MongoDB很多時候會做隨機訪問。對于隨機訪問,這個預讀值應該設置的較小為好,一般來說32是一個不錯的選擇。
你可以使用下述命令來顯示當前系統的預讀值:
sudo blockdev --report
要更改預讀值,可以用以下命令:
sudo blockdev --setra 32
把換成合適的存儲設備。
3.13deadline
文件系統上還有一個提高IO的優化萬能鑰匙,那就是deadline。
在 Flash技術之前,我們都是使用機械磁盤存儲數據的,機械磁盤的尋道時間是影響它速度的最重要因素,直接導致它的每秒可做的IO(IOPS)非常有限, 為了盡量排序和合并多個請求,以達到一次尋道能夠滿足多次IO請求的目的,Linux文件系統設計了多種IO調度策略,已適用各種場景和存儲設備。
Linux的IO調度策略包括:Deadline scheduler,Anticipatory scheduler,Completely Fair Queuing(CFQ),NOOP。
每種調度策略的詳細調度方式我們這里不詳細描述,這里我們主要介紹CFQ和Deadline,CFQ是Linux內 核2.6.18之后的默認調度策略,它聲稱對每一個 IO 請求都是公平的,這種調度策略對大部分應用都是適用的。
但是如果數據庫有兩個請求,一個請求3次IO,一個請求10000次IO,由于絕對公平,3次IO的這個請求都需要跟其他10000個IO請求競爭,可能要等待上千個IO完成才能返回,導致它的響應時間非常慢。并且如果在處理的過程中,又有很多IO請 求陸續發送過來,部分IO請求甚至可能一直無法得到調度被“餓死”。而deadline兼顧到一個請求不會在隊列中等待太久導致餓死,對數據庫這種應用來 說更加適用。
實時設置,我們可以通過
echo "deadline" > /sys/block/sda/queue/scheduler
來將sda的調度策略設置為deadline,我們也可以直接在/etc/grub.conf的kernel行最后添加elevator=deadline來永久生效。
3.14 使用NTP時間服務器
在使用MongoDB復制集或者分片集群的時候,注意一定要使用NTP時間服務器。這樣可以保證MongoDB集群成原則之間正確同步。
四、索引設計
4.1 為你的每一個查詢建立合適的索引
這個是針對于數據量較大比如說超過幾十上百萬(文檔數目)數量級的集合。如果沒有索引MongoDB需要把所有的Document從盤上讀到內存,這會對MongoDB服務器造成較大的壓力并影響到其他請求的執行。
4.2 創建合適的組合索引,不要依賴于交叉索引
如果你的查詢會使用到多個字段,MongoDB有兩個索引技術可以使用:交叉索引和組合索引。交叉索引就是針對每個字段單獨建立一個單字段索引,然后在查詢執行時候使用相應的單字段索引進行索引交叉而得到查詢結果。交叉索引目前觸發率較低,所以如果你有一個多字段查詢的時候,建議使用組合索引能夠保證索引正常的使用。
例如,如果應用需要查找所有年齡小于30歲的深圳市馬拉松運動員:
db.athelets.find({sport: "marathon", location: "sz", age: {$lt: 30}}})
那么你可能需要這樣的一個索引:
db.athelets.ensureIndex({sport:1, location:1, age:1});
4.3 組合索引字段順序:匹配條件在前,范圍條件在后(Equality First, Range After)
以上文為例子,在創建組合索引時如果條件有匹配和范圍之分,那么匹配條件(sport: “marathon”) 應該在組合索引的前面。范圍條件(age: <30)字段應該放在組合索引的后面。
4.4 盡可能使用覆蓋索引(Covered Index)
有些時候你的查詢只需要返回很少甚至只是一個字段,例如,希望查找所有虹橋機場出發的所有航班的目的地。已有的索引是:
{origin: 1, dest: 1}
如果正常的查詢會是這樣(只需要返回目的地機場):
db.flights.find({origin:"hongqiao"}, {dest:1});
這樣的查詢默認會包含_id 字段,所以需要掃描匹配的文檔并取回結果。相反,如果使用這個查詢語句:
db.flights.find({origin:"hongqiao"}, {_id:0, dest:1});
MongoDB則可以直接從索引中取得所有需要返回的值,而無需掃描實際文檔(文檔可能需要從硬盤里調入到內存)。
4.5 建索引要在后臺運行
在對一個集合創建索引時,該集合所在的數據庫將不接受其他讀寫操作。對數據量的集合建索引,建議使用后臺運行選項 {background: true}。
五、監控與備份
5.1 對重要的數據庫指標進行監控及告警
關鍵的指標包括:
– Disk Space磁盤空間
– CPU
– RAM使用率
– Ops Counter增刪改查
– Replication Lag復制延遲
– Connections連接數
– Oplog Window
5.2?對慢查詢日志進行監控
默認情況下MongoDB會在日志文件中(mongod.log)記錄超過100ms的數據庫操作。
六、模式設計
6.1 不要按照關系型來設計表結構
MongoDB可以讓你像關系型數據庫一樣設計表結構,但是它不支持外鍵,也不支持復雜的Join!如果你的程序發現有大量實用JOIN的地方,那你的設計可能需要重新來過。參照以下相關模式設計建議。
6.2 數據庫集合(collection)的數量不宜太多
MongoDB的模式設計基于靈活豐富的JSON文檔模式。在很多情況下,一個MongoDB應用的數據庫內的集合(表)的數量應該遠遠小于使用關系數據庫的同類型應用。MongoDB表設計不遵從第三范式。MongoDB的數據模型非常接近于對象模型,所以基本上就是按照主要的Domain object的數量來建相應的集合。根據經驗,一般小型應用的集合數量通常在幾個之內,中大型的應用會在10多個或者最多幾十個。
6.3 不要害怕數據冗余
MongoDB模式設計不能按照第三范式,很多時候允許數據在多個文檔中重復,比如說,在每一個員工的文檔中重復他的部門名字,就是一個可以接受的做法。如果部門名字改了,可以執行一個update({},{}, {multi:true}) 的多文檔更新來一次性把部門名字更新掉。
6.4 適合和不適合冗余的數據類型
一般來說,如果某個字段的數據值經常會變,則不太適合被大量冗余到別的文檔或者別的集合里面去。舉例來說,如果我們是在做一些股票類型資產管理, 可能有很多人都購買了Apple的股票,但是如果把經常變動的股價冗余到客戶的文檔里,由于股票價格變動頻繁,會導致有大量的更新操作。從另外一個角度來說,如果是一些不經常變的字段,如客戶的姓名,地址,部門等,則可以盡管進行冗余shi’yang
對 1:N(一些)的關系使用全部內嵌
對于一對多的關系,如一個人有幾個聯系方式,一本書有10幾個章節,等等,建議使用內嵌方式,把N的數據以數組形式來描述,如:
> db.person.findOne()
{
??????user_id: 'tjworks',
??????name: 'TJ Tang',??????????
??????contact : [
???????? { type: 'mobile', number: '1856783691' },
???????? { type: 'wechat', number: 'tjtang826'}
??????]
}
對 1: NN (很多) 的關系使用ID內嵌
有些時候這個一對多的多端數量較大, 比如說,一個部門內有多少員工。在華為一個三級部門可能有數千員工,這個時候如果把所有員工信息直接內嵌到部門內肯定不是個好的選擇,有可能會超出16MB的文檔限制。這個時候可以采用引用ID的方式:
> db.departments.findOne()
{
????name : 'Enterprise BG',
????president: 'Zhang San',
????employees : [???? // array of references to Employee colletion
????????ObjectID('AAAA'),????
????????ObjectID('F17C'),????
????????ObjectID('D2AA'),
????????// etc
????]
}
如果需要查詢部門下員工相關信息,你可以使用$lookup聚合操作符來把員工信息進行關聯并返回。
對 1: NNN (很多) 的關系使用
如果一對多情況下,這個多端數量無限大并會頻繁增長,比如說,一個測量儀的每分鐘讀數,一年下來有幾十萬條,這個時候即使是把ID放到數組里都會管理不便,這個時候就應該把多端的數據創建一個集合,并在那個集合的文檔里加入對主文檔的連接引用,如:
> db.sensors.findOne()
{
????????_id : ObjectID('AAAB'),
????????name : 'engine temperature',
????????vin : '4GD93039GI239',
????????engine_id: '20394802',
????????manuafacture: 'First Motor',
????????production_date: '2014-02-01'
????????...
????}
????>db.readings.findOne()
????{
????????time : ISODate("2014-03-28T09:42:41.382Z"),
????????sensor: ObjectID('AAAB'),
????????reading: 67.4????????????
}
6.5 把二進制大文件和元數據分集合存放
如果你有需要把PDF文件,圖片,甚至小視頻等二進制文件需要管理,建議使用MongoDB 的GridFS API 或者自己手動分集合來分開管理二進制數據和元數據。
6.6 經常更新的數據不要放在嵌套數組內
數組是用來表達 1對多關系的利器,但是MongoDB對嵌套的數組內元素缺乏直接更新能力。比如說:
{
????name: "Annice",
????courses: [
????????{ name: "English", score: 97 },
????????{ name: "Math", score: 89 },
????????{ name: "Physics", score: 95 }
????]
}
這樣設計沒有嵌套數組,我們可以直接對 Math的score 修改為99:
db.students.update({name: "Annice", "courses.name":"Math"}, {$set:{"courses.$.score": 99 }})
注意數組定位符 $ 的用法,$ 表示當前匹配的第一個數組元素的在數組內的索引。
但是下面這種情況就涉及到了數組嵌套:
{
????????name: "Annice",
????????courses: [
????????????{ name: "Math", scores: [
????????????????????????????????{term: 1, score: 80} ,
????????????????????????????????{term: 2, score: 90}
????????????????????????????]
???????????? },
????????????{ name: "Physics", score: 95 }
????????]
}
這個時候如果你想對Math course的term 1的Score進行修改,你就需要把 scores 這個數組整個調到內存然后在代碼里對這個嵌套數組的元素進行修改。這是因為MongoDB的數組定位符 $ 只對第一層數組有效。
當然,如果你的模型不需要修改嵌套的數組內元素,那么這條就不適用。
七、程序配置
7.1 設定合適的MongoDB連接池大小 (Connections Per Host)
Java驅動的默認連接池大小是100。建議按照應用的實際情況做調整。對壓力較小的應用可以適當調小減少對應用服務器的資源占用。
7.2 正確使用寫關注設置(Write Concern)
MongoDB的建議最小部署是一個復制集,包含3個數據節點。默認情況下應用的寫操作(更新,插入或者刪除)在主節點上完成后就會立即返回。寫操作則通過OPLOG方式在后臺異步方式復制到其他節點。在極端情況下,這些寫操作可能還未在復制到從節點的時候主節點就出現宕機。這個時候發生主備節點切換,原主節點的寫操作會被回滾到文件而對應用不可見。為防止這種情況出現,MongoDB建議對重要的數據使用 {w: “marjority”} 的選項。{w: “majority”} 可以保證數據在復制到多數節點后才返回成功結果。使用該機制可以有效防止數據回滾的發生。
另外你可以使用 {j:1} (可以和 w:”majrotiy” 結合使用) 來指定數據必須在寫入WAL日志之后才向應用返回成功確認。這個會導致寫入性能有所下降,但是對于重要的數據可以考慮使用。
7.3 正確使用讀選項設置(Read Preference)
MongoDB由于是一個分布式系統,一份數據會在多個節點上進行復制。從哪個節點上讀數據,要根據應用讀數據的需求而定。以下是集中可以配置的讀選項:
primary: 默認,在主節點上讀數據。
priaryPreferred: 先從主節點上讀,如果為成功再到任意一臺從節點上讀。
secondary: 在從節點上讀數據(當有多臺節點的時候,隨機的使用某一臺從節點)。
secondaryPreferred: 首先從從節點上讀,如果從節點由于某種原因不能提供服務,則從主節點上進行讀。
nearest: 從距離最近的節點來讀。距離由ping操作的時間來決定。
除第一個選項之外,其他讀選項都存在讀到的數據不是最新的可能。原因是數據的復制是后臺異步完成的。
7.4 不要實例化多個MongoClient
MongoClient是個線程安全的類,自帶線程池。通常在一個JVM內不要實例化多個MongoClient實例,避免連接數過多和資源的不必要浪費。
7.5 對寫操作使用Retry機制
MongoDB使用復制集技術可以實現99.999%的高可用。當一臺主節點不能寫入時,系統會自動故障轉移到另一臺節點。轉移可能會耗時幾秒鐘,在這期間應用應該捕獲相應的Exception并執行重試操作。重試應該有backoff機制,例如,分別在1s,2s,4s,8s等時候進行重試。
7.6 避免使用太長的字段名
MongoDB 沒有表結構定義。每個文檔的結構由每個文檔內部的字段決定。所有字段名會在每個文檔內重復。使用太長的字段名字會導致對內存、網絡帶寬更多的需求。(由于壓縮技術,長字段名對硬盤上的存儲不會有太多占用)
7.7 使用有規律的命名方式
如: School, Course, StudentRecord
或者:school, course, stuent_record
7.8 正確使用更新語句
不要把MongoDB和普通的鍵值型數據庫(KV)視為等同。MongoDB支持和關系型數據庫update語句類似的in place update。你只需要在update語句中指定需要更新的字段,而不是整個文檔對象。
舉例來說,加入我想把用戶的名字從TJ改為Tang Jianfa.
不建議的做法:
user = db.users.findOne({_id: 101});
user.name="Tang Jianfa"
db.users.save(user);
建議的做法:
user = db.users.findOne({_id: 101});????????
// do certain things
db.users.update({_id:101}, {$set: {name: "Tang Jianfa"}});
7.8 使用投射(projection)來減少返回的內容
MongoDB 支持類似于SQL語句里面的select,可以對返回的字段進行過濾。使用Projection可以減少返回的內容,降低網絡傳輸的量和代碼中轉化成對象所需的時間。
7.9 使用TTL來自動刪除過期的數據
很多時候我們用MongoDB來存儲一些時效性的數據,如7天的監控數據。與其自己寫個后臺腳本定期清理過期數據,你可以使用TTL索引來讓MongoDB自動刪除過期數據:
db.data.ensureIndex({create_time:1}, {expireAfterSeconds: 7*24*3600})
7.10 使用execute命令來實現upsert
有些時候你不知道一條文檔數據是否已經在庫里存在。這個時候你要么先查詢一下,要么就是使用upsert語句。在SpringData下面upsert語句需要你把每個字段的值都在upsert語句中格式化出來。字段多的時候未免有些繁瑣。SpringData MongoDB里面的MongoTemplate有個execute方法可以用來實現一個DB調用,也不用繁瑣的把所有字段羅列出來的例子。
public boolean persistEmployee(Employee employee) throws Exception {
????????BasicDBObject dbObject = new BasicDBObject();
????????mongoTemplate.getConverter().write(employee, dbObject);
????????mongoTemplate.execute(Employee.class, new CollectionCallback() {
????????????public Object doInCollection(DBCollection collection) throws MongoException, DataAccessException {
????????????????collection.update(new Query(Criteria.where("name").is(employee.getName())).getQueryObject(),
????????????????????????dbObject,
????????????????????????true,??// means upsert - true
????????????????????????false??// multi update – false
????????????????);
????????????????return null;
????????????}
????????});
????????return true;
}
7.11 刪除SpringData MongoDB下面的_class字段
SpringData MongoDB默認會在MongoDB文檔中添加一個_class字段,里面保存的是fully qualified class name, 如”com.mongodb.examples.Customer”。對于有些小文檔來說,這個字段可能會占據不小一部分的存儲空間。如果你不希望SpringData 自動加入這個字段,你可以:
1) 自定義MongoTypeMapper
@Bean
public MongoTemplate mongoTemplate() throws UnknownHostException {
????MappingMongoConverter mappingMongoConverter =??new MappingMongoConverter(new DefaultDbRefResolver
????????????(mongoDbFactory()), new
????????????MongoMappingContext());
????mappingMongoConverter.setTypeMapper(new DefaultMongoTypeMapper(null));
????return new MongoTemplate(mongoDbFactory(), mappingMongoConverter );
}
2) 在使用find語句時,顯式地指定類的名字/類型:
MongoTemplate.find(new Query(), Inventory.class))
摘自:http://www.ywnds.com/?p=8656