戴傳飛,馬明棟
(1.南京郵電大學 通信與信息工程學院,江蘇 南京 210003;2.南京郵電大學 地理與生物信息學院,江蘇 南京 210023)
大數據時代,越來越多不同內容和形式的數據涌現出來,半結構化數據和非結構化數據的使用使當前系統應用越來越豐富多彩。面對海量數據的存儲,原始的關系型數據庫已經顯得力不從心[1]。大數據的出現以及云計算的盛行使NoSQL這種非結構化數據庫越來越受到重視[2]。盡管數據庫技術發生了巨大變革,但是查詢操作仍然是最頻繁的數據庫操作,因此研究數據庫的查詢及其優化變得十分重要。作為NoSQL之一的MongoDB,因其在處理高并發、大數據等領域的優勢而備受青睞[3]。文中將主要關注MongoDB的查詢優化,針對skip_limit分頁查詢技術的不足提出一種新的分頁查詢算法[4]。
MongoDB是一個基于分布式文件存儲的開源數據庫系統。在高負載的情況下,添加更多的節點,可以保證服務器性能。MongoDB旨在為Web應用提供可擴展的高性能數據存儲解決方案。
MongoDB將數據存儲為一個文檔,數據結構由鍵值對組成,字段值可以包含其他文檔,數組及文檔數組[5]。MongoDB文檔類似于JSON對象的BSON。BSON是為效率而設計的,只需要使用很少的空間,同時其編碼和解碼都是非常快速的。即使在最壞的情況下,BSON格式也比JSON格式在最好的情況下存儲效率高。
MongoDB的主要功能特性如下:
(1)面向集合存儲,容易存儲對象類型的數據。在MongoDB中數據被分組存儲在集合中,集合類似RDBMS中的表,并且一個集合中可以存儲無限多的文檔[4]。
(2)模式自由,采用無模式結構存儲。
(3)支持完全索引,可以在任意屬性上建立索引,包含內部對象。MongoDB的索引和RDBMS的索引基本一樣,可以在指定屬性、內部對象上創建索引以提高查詢速度[6]。
(4)強大的聚合工具。MongoDB除了提供豐富的查詢功能外,還提供強大的聚合工具,如count、group等,同時支持使用MapReduce完成復雜的聚合任務[7]。
(5)支持復制和數據恢復。MongoDB支持主從復制機制,可以實現數據備份、故障恢復、讀擴展等功能。
(6)使用高效的二進制數據存儲,包括大型對象(如視頻)[8]。使用二進制格式存儲,可以保存任何類型的數據對象。
(7)自動處理分片,以支持云計算層次的擴展。MongoDB支持集群自動切分數據,對數據進行分片可以使集群存儲更多的數據,實現更大的負載,也能保證存儲的負載均衡[9]。
(8)支持Perl、PHP、Java、C#、JavaScript、Ruby、C和C++語言的驅動程序[10],開發人員使用任何一種主流開發語言都可以輕松編程。
隨著信息技術的高速發展,應用系統中的數據量越來越龐大,高效的查詢方法顯得尤為重要。一般來講,不要將查詢獲得的數據一次性全部顯示出來,因為這樣會耗費大量的查詢時間[11]。當面對海量數據時,將大量數據直接顯示出來會給用戶瀏覽帶來極大的不便,對系統性能也會造成嚴重的影響。再者,一次性將大量的數據發往客戶端,會增加網絡帶寬的負擔,延緩系統的反應速度,降低系統的性能。面對這些問題,采取分頁方式來顯示數據就變得很重要。在數據庫查詢中對數據進行分頁顯示是十分必要的。
與關系型數據庫中的分頁方法類似,MongoDB數據庫也可以使用skip_limit方法進行數據庫分頁。針對小型用戶系統,使用skip_limit分頁方法確實可以迅速地查詢出指定數據,并且不會對分頁響應速度產生太大影響。但是,如果系統中存儲的數據量變得非常龐大,skip操作就會隨之變得非常慢,從而嚴重影響數據庫查詢效率。引發這種狀況的主要原因是:skip操作需要先找到被略過的數據,然后再將這些數據進行拋棄。目前很多數據庫都會將大量的元數據保存在索引當中,其目的是為了減輕skip函數的工作量。雖然這種方法非常有用,但是目前的MongoDB數據庫并不支持這一操作,因此有必要使用新的分頁方法來處理海量數據查詢。
在當前Web應用開發中,數據庫分頁技術是經常使用的一種數據表示方法,數據分頁效率的好壞極大地影響了系統性能,已經被作為數據庫評判的重要指標[12]。不僅如此,分頁顯示速度的快慢會對Web應用的性能和網絡服務質量產生極大的影響。
使用skip_limit方法進行分頁查詢時,首先會根據查詢條件查找出所有的結果集;然后使用skip函數跳過指定數量的數據;最后通過limit函數來限制顯示記錄的數量。
在常用的查詢語句中,經常會使用find函數來查找指定條件的結果集,使用limit函數限制返回的結果數,使用skip函數跳過指定條數的記錄。通常在數據庫查詢中使用分頁查詢時還會使用sort方法,該方法可以對查詢記錄進行排序,這為數據分頁查詢提供了極大的便利。這三種方法可以組合使用,對于分頁非常有效。
使用skip_limit分頁方法在test數據庫的users集合中分別查詢第1頁、第2頁、第n頁年齡為20、21、22的用戶數據,有關查詢語句如下所示:
第1頁:
db.users.find({“age”:{“$in”:[20,21,22]}}).skip(0).limit(10)
第2頁:
db.users.find({“age”:{“$in”:[20,21,22]}}).skip(10).limit(10)
第n頁:
db.users.find({“age”:{“$in”:[20,21,22]}}).skip((n-1)*10).limit(10)
通用公式可以表示為:
db.集合名稱.find(查詢條件).skip((頁碼-1)*每頁記錄數).limit(每頁記錄數)
上述公式首先使用find函數查詢當前數據庫中指定條件的集合數據,即查找出users集合中的所有數據(注意find方法中可包含查詢條件);然后使用skip方法略過指定的文檔數,即略去當前頁之前的所有數據;最后使用limit方法來限制每頁需要顯示多少條記錄,該實驗為10條。一般在進行分頁查詢時還會使用sort對結果集進行排序。
MongoDB數據庫采用內置的skip+limit分頁方法,對于處理少量的數據,這種分頁的確能起到不錯的效果,查詢速度也能得以提升[13]。但是,當存儲的數據量達到百萬級別甚至更大時,內置的分頁方法就變得難以處理,它會導致頁面數據獲取速度變得極其緩慢,系統性能因而受到極大的影響[14]。造成該結果的原因是如果查詢的數據位于排序的集合后面,此時使用skip函數就需要跳過很大的數據量,在這種情況下仍使用skip函數就會極大地影響數據查詢效率。也就是說在大數據下,skip_limit的分頁查詢已經不再適用。
面對海量數據查詢,針對skip_limit方法存在的問題,為提升數據庫的查詢效率,文中提出了一種新的分頁方法—where_limit。與skip_limit方法的原理不同,where_limit算法不再以數據偏移量為核心,它通過尋找出分頁信息中當前所在頁的上一頁的數據標記或關鍵詞,然后以該關鍵詞為基礎進行條件查詢,只需使查詢語句中的條件參數大于這個關鍵詞,就能決定最后到底需要limit到多少條數據,從而實現分頁查詢。考慮到關鍵詞數組的連續性,需要在MongoDB數據庫中創建一個連續的索引,因為只有在連續索引中,才能成功找到指定的關鍵詞。
利用where_limit分頁方法進行數據查詢時,首先通過查詢條件來獲取文檔中所有數據的關鍵詞,然后將其讀取到相應數組中,然后根據關鍵詞數組的下標來確定需要跳過的記錄數,接著使用limit方法來限定顯示的記錄數目,最后使用sort方法對結果排序。這種分頁算法的核心思想便是犧牲空間來提高查詢效率。雖然需要用比較大的存儲空間去存放關鍵詞數組,但是如果能極大地提升查詢效率,這種分頁方法也是非常可行的。
以用戶基本信息為例來測試where_limit方法是否優于skip_limit方法。為進行數據分頁查詢,需要先在用戶信息系統中實現where_limit方法,可以將其分為三步:
(1)使用count函數確定數據庫中總的記錄數的長度,然后根據每頁需要顯示的記錄數來計算出數據需要顯示的總頁數;
(2)根據查詢條件獲取關鍵詞數組;
(3)通過關鍵詞數組中的關鍵詞進行分頁查詢。
where_limit方法要優于skip_limit方法,那是因為它避免使用skip函數,系統不需要花費大量時間來跳過大量數據。使用skip_limit方法進行分頁查詢時,會優先考慮分頁的數據偏移量(skip函數的參數值),隨著偏移量不同,每頁查詢的時間就會不同:偏移量越大,查詢時間也就越長。where_limit為解決這一問題,使用了查找關鍵詞數組的方法:在查詢過程中,用戶只需要根據查詢條件尋找出關鍵詞數組,然后根據數組中關鍵詞的下標來決定要跳過的記錄數,這樣就避免使用skip函數。算法處理流程如圖1所示。

圖1 where_limit算法處理流程
為驗證where_limit算法的可行性,通過MongoDB自帶的Mongo shell向數據庫名為test,集合名為users的集合中插入100萬條數據。集合中文檔結構如下:
{
“_id”:ObjectId(‘584e58e46dadd1120d4f75345’),
“name”:“Siri”,
“age”:35,
“sex”:“woman”,
“num”:1
}
users集中存放有100萬條記錄,分頁時限制在每頁顯示100條記錄。
使用skip_limit方法和where_limit方法分別對1 000頁和8 000頁數據進行查詢,可得出通過where_limit方法對應的查詢條件分別是num值為100 000、800 000,使用MongoDB性能分析工具explain()進行分析,實驗結果如圖2~5所示。

圖2 使用skip_limit方法查詢第1 000頁數據

圖3 使用where_limit方法查詢第1 000頁數據

圖4 使用skip_limit方法查詢第8 000頁數據

圖5 使用where_limit方法查詢第8 000頁數據
從以上圖中可以發現,查詢第1 000頁、8 000頁數據,skip_limit方法所需時間為65 ms、402 ms,而使用where_limit方法所需時間接近0 ms、0 ms。使用where_limit方法的查詢耗時遠小于使用skip_limit方法,優化效果明顯。
為了提高實驗數據的可靠性,使用多臺虛擬機進行實驗,采用取平均值的方法,對兩種分頁方法進行對比,測試結果如圖6所示。

圖6 兩種分頁方法效率對比
由實驗結果可知,skip_limit方法在skip方法跳過記錄數越大時,數據查詢所耗費的時間越長,因此查詢效率越低;而where_limit方法由于每次只返回特定頁面的數據,不再使用skip函數,分頁速度快且穩定可靠,查詢效率明顯優于skip_limit方法。
MongoDB內置的skip操作在小數據的情況下實現分頁技術簡單,但是在數據量達到百萬級別甚至更大時,skip操作的弊端愈加明顯。數據庫的分頁查詢效率成為影響數據庫訪問性能的重要因素。通過分析MongoDB內置的skip_limit分頁方法的優缺點及影響分頁查詢速度的關鍵因素,提出一種新的數據分頁方法—where_limit。通過改變查詢文檔的規則及使用合理的索引來提高分頁效率。實驗結果表明,優化后的查詢方法在實現分頁顯示的操作中速度有明顯的提高。
參考文獻:
[1] 程顯峰.MongoDB權威指南[M].北京:人民郵電出版社,2011.
[2] 金 鑫.非結構化數據查詢處理與優化[D].杭州:浙江大學,2015.
[3] HUANG Yu,LUO Tiejian.NoSQL database:a scalable,availability,high performance storage for big data[M].[s.l.]:Springer International Publishing,2014.
[4] 潘 凡.從MySQL到MongoDB-視覺中國的NoSQL之路[J].程序員,2010(6):79-81.
[5] STEVIC M P,MILOSAVLJEVIC B,PERISIC B R.Enhancing the management of unstructured data in e-learning systems using MongoDB[J].Program Electronic Library and Information Systems,2015,49(1):30-45.
[6] 吳德寶.關系與非關系數據庫應用對比研究[D].撫州:東華理工大學,2015.
[7] 梁云柯.MongoDB索引機制研究[D].重慶:重慶郵電大學,2016.
[8] 沈 姝.NoSQL數據庫技術及其應用研究[D].南京:南京信息工程大學,2012.
[9] 祁 蘭.基于MongoDB的數據存儲與查詢優化技術研究[D].南京:南京郵電大學,2016.
[10] 呂明育,李小勇.NoSQL數據庫與關系數據庫的比較分析[J].微型電腦應用,2011,27(10):55-58.
[11] 郭忠南,孟凡榮.關系數據庫性能優化研究[J].計算機工程與設計,2006,27(23):4484-4486.
[12] 丁智斌,石浩磊.關系數據庫設計與規范化[J].計算機與數字工程,2005,33(2):114-116.
[13] BACH M,WERNER A.Standardization of NoSQL database languages[M].[s.l.]:Springer International Publishing,2014.
[14] 王光磊.MongoDB數據庫的應用研究和方案優化[J].中國科技信息,2011(20):93-94.