最近在設計的Log Service中需要用到Mongodb這個Nosql資料庫(不知道Mongodb的點我),由於是用於純存日誌,而且日誌量巨大,百萬千萬級的,所以需要用到它的分頁查詢。
不過LZ也是剛剛接觸這個資料庫,不是很瞭解裡面的命令文法,便在網上查了一些資料,結果 結果說mongodb內建的簡單很方便的Skip方式的分頁效率很低,無奈,無奈得用其他的,
有多篇文章都推薦Where+Limit的方式分頁,說他效率比Skip方式高多了,但是好多資料都是講一些思路,並沒有很具體,但是也很有協助拉,現在簡單的來講一下這個分頁思路(Skip的方式那麼簡單就不講啦):
假設一張表中(Mongodb用集合來代替)有如下條資料:1,3,4,5,6,7,8,9,20,30,50,51,52,59,60(僅僅標誌該記錄的ID號 你可以理解為主鍵)
現在的也尺寸PageSize=4,那麼
- 第一頁的資料為1,3,4,5,這個用where的方式解釋為SQL語句為Select top 4 * from table where id>0 因為上一頁是沒有記錄 所以用0來代替
- 第二頁的資料為6,7,8,9,20,這個用where的方式解釋為SQL語句為Select top 4 * from table where id>5 這裡的5就是上一頁的最後一條記錄
- 第二頁的資料為30,50,51,52,,這個用where的方式解釋為SQL語句為Select top 4 * from table where id>20 這裡的20就是第二頁的最後一條記錄
這下就簡單了,以後需要分頁查詢的時候傳上一個ID號即可,Mongodb裡面的思路也是這樣 不過不一樣的是c#用mongodb需要用其他驅動來查詢資料,就用不了SQL語句了,簡單的來貼一下代碼
View Code
/// <summary> /// 分頁查詢 指定索引最後項-PageSize模式 /// </summary> /// <typeparam name="T">所需查詢的資料的實體類型</typeparam> /// <param name="query">查詢的條件 沒有可以為null</param> /// <param name="indexName">索引名稱</param> /// <param name="lastKeyValue">最後索引的值</param> /// <param name="pageSize">分頁的尺寸</param> /// <param name="sortType">排序類型 1升序 -1降序 僅僅針對該索引</param> /// <param name="collectionName">指定的集合名稱</param> /// <returns>返回一個List列表資料</returns> public List<T> Find<T>(IMongoQuery query, string indexName, object lastKeyValue, int pageSize, int sortType, string collectionName) { MongoCollection<T> mc = this._db.GetCollection<T>(collectionName); MongoCursor<T> mongoCursor = null; query = this.InitQuery(query); //判斷升降序後進行查詢 if (sortType > 0) { //升序 if (lastKeyValue != null) { //有上一個主鍵的值傳進來時才添加上一個主鍵的值的條件 query = Query.And(query, Query.GT(indexName, BsonValue.Create(lastKeyValue))); } //先按條件查詢 再排序 再取數 mongoCursor = mc.Find(query).SetSortOrder(new SortByDocument(indexName, 1)).SetLimit(pageSize); } else { //降序 if (lastKeyValue != null) { query = Query.And(query, Query.LT(indexName, BsonValue.Create(lastKeyValue))); } mongoCursor = mc.Find(query).SetSortOrder(new SortByDocument(indexName, -1)).SetLimit(pageSize); } return mongoCursor.ToList<T>(); }
當然這個程式碼片段不怎麼好看,估計各位讀者看不大清,放心,下面會附源碼下載(最恨那種代碼貼一半都不知道說什麼的人了)
既然他們都說Skip效率差,那就自己測試看看唄,眼見為實嘛,
我先在Mongodb從添加1000W條簡單的資料,大資料量下測試才有有效果嘛,
給看下測試的控制台代碼吧,都封裝好了看的很方便哦,懶的展開的就不要了,很簡單的
View Code
class Program { static MongoDBHelper db; static void Main(string[] args) { //建立Mongodb的資料庫執行個體 db = new MongoDBHelper(); #region 1000W條資料的初始化 //InitData(); #endregion Console.WriteLine("Mongodb 中自己的Skip-Limit分頁與自訂的Where-Limit分頁效率測試(毫秒):"); //各種分頁 尺寸的測試 具體注釋我也不寫了 PagerTest(1, 100);//這個測試忽略,估計第一次查詢之後會相應的緩衝下資料 導致之後的查詢很快 PagerTest(3, 100); PagerTest(30, 100); PagerTest(300, 100); PagerTest(300, 1000); PagerTest(3000, 100); PagerTest(30000, 100); PagerTest(300000, 100); Console.ReadKey(); } /// <summary> /// 分頁的測試 /// </summary> /// <param name="pageIndex">頁碼</param> /// <param name="pageSize">頁尺寸</param> static void PagerTest(int pageIndex,int pageSize) { //分頁查詢條件空(封裝中會轉恒真條件) 排序條件空(轉為ObjectId遞增) 設定頁碼 也尺寸 Console.WriteLine("頁碼{0},頁尺寸{1}", pageIndex, pageSize); Stopwatch sw1 = new Stopwatch(); sw1.Start(); List<LogInfo> list1 = db.Find<LogInfo>(null, pageIndex, pageSize, null); sw1.Stop(); Console.WriteLine("Skip-Limit方式分頁耗時:{0}", sw1.ElapsedMilliseconds); Stopwatch sw2 = new Stopwatch(); sw2.Start(); //這裡以Logid索引為標誌 如果集合裡面沒有這些主鍵標誌的話 完全可以使用自己的ObjectId來做 協助類裡面也是封裝好的 //根據頁碼計算的LogId也只是簡單的類比 實際中這些LogId不一定會連續 這種方式分頁一般不是傳頁碼 而是傳最後一個標誌的值 List<LogInfo> list2 = db.Find<LogInfo>(null, "LogId", (pageIndex - 1) * pageSize, pageSize, 1); sw2.Stop(); Console.WriteLine("Where-Limit方式分頁耗時:{0}\r\n", sw2.ElapsedMilliseconds); } /// <summary> /// 初始化一下資料 /// </summary> static void InitData() { //建立 測試日誌類的索引 索引的配置在LogInfo類的特性中 db.CreateIndex<LogInfo>(); //初始化日誌的集合 List<LogInfo> list = new List<LogInfo>(); int temp = 0; //插入1000W條 測試的資料 for (int i = 1; i <= 10000000; i++) { list.Add(new LogInfo { LogId = i, Content = "content" + i.ToString(), CreateTime = DateTime.Now }); //temp計數 並作大於100的判斷 if (++temp >= 100) { //大於等於100就清零 temp = 0; //用封裝好的方法批量插入資料 db.Insert<LogInfo>(list); //插入資料之後將當前資料清空掉 list.Clear(); } } } }
來看下最終的效率測試圖吧:
非常 ,very,超級明顯的可以看出來Skip-Limit的分頁效率有多低了吧,每當頁碼增加十倍時速度就降低十倍,在30W頁的時候查詢一次竟然要30秒,在大資料量下查詢時完全受不了了,然而where-Limit的那種不管你多少頁,速度還是那麼快,最後一條的0秒是被四捨五入進0的,你看到了多塊了吧。
連續測試這幾都是這幾種情況,都不想把表格或者圖來看了(第一條測試資料可以忽略,估計第一次查詢會慢一點,以後會緩衝)
當然了,Where-Limit的方式查詢是快,但是實際做起來還是有點麻煩得,不是傳頁碼,而是傳上一頁的標誌,並且並不是所有的集合都有自己的主鍵的,沒有的話你可以用mongodb內建的ObjectId來查,他是預設的索引,速度也是很快的。
建議如果是小量資料幾千幾萬條的話 用Skip也無妨啦,畢竟是方便,如果資料量大的話千萬別用,危險!!!!
猛擊我去看源碼,可以直接運行哦,裡面還有我自己寫的Mongodb 查詢協助類
參考的文章:
MongoDB不使用skip做分頁
使用mongodb做分頁/排名查詢時的效能問題
mongodb中分頁顯示及其啟發