分表情況下的分頁如何優化

首先還是要給自己的開原框架打個廣告 sharding-core 針對efcore 2+版本的分表元件,首先我們來快速回顧下目前市面上分表下針對分頁常見的集中解決方案

分表解決方案

解決方案 skip<=100 skip<10000 skip>10000 優點 缺點
記憶體分表 速度快O(n),n=skip*分表數 速度快O(n),n=skip*分表數,記憶體暴漲 O(n),n=skip*分表數,記憶體爆炸,速度越來越慢 實現簡單,支援分庫 skip過大記憶體暴漲
union all 速度快 速度一般 死慢死慢的 實現簡單 僅支援同庫,不好優化,索引會失效
流式分表 速度快O(n),n=skip 速度快O(n),n=skip O(n),n=skip 速度越來越慢 支援分庫 實現複雜

1.記憶體分頁

顧名思義就是將各個表的結果集合併到記憶體中進行排序後分頁

2.union all

使用的是資料庫本身的聚合操作,用過匿名錶來實現和操作當前表一樣無感知

3.流式分表

和名字一樣就是通過next來一次一次獲取,和datareader類似只有在next後才可以獲取到客戶端

通過上面的簡單對照我們可以清楚地發現,其實我們可以選擇的基本上就記憶體分表和流式分表而已,又以為記憶體分表的限制其實最優解就是流式分表。

上篇文章我們簡單的介紹了流式分表這次我們在針對流式分表的原理進行介紹,並且提出針對流式分表下的分頁“最優解”。

流式分表原理

我們先簡單的假設一個場景,我們有一個訂單表,針對訂單表我們進行了分表,根據訂單的建立時間按月分表。

如果我們執行 select * from order limit 100,2

記憶體分頁

在這種情況下如果我們需要分頁跳過前 100條記錄獲取第101-102條記錄,現在如果記憶體分表情況下我們該如何操作

流式分頁

上述就是記憶體排序的實現,通過上圖發現我們需要獲取102*3條資料,並且進行排序後獲取第101和102條資料,所以說上述表格裡已經體現了記憶體分表的優劣
那麼如果是流式分頁我們是如何操作的呢

簡單解釋下這張圖,右邊為資料庫在資料庫外面的分別是next了一次的資料,其他資料都是在資料庫裡面只是結果集有了但是結果還不沒有取到client,

通過100次next後我們可以取到真實的資料所以對於任何分頁都是隻需要O(n)的時間複雜度,其中n=skip+take就是跳過多少條和獲取多少條

注意:不要以為next了100次就是查詢了100次資料庫,結果集生成後就不會再查詢資料庫裡,next可以理解為是對結果集的客戶端獲取。

sharding-core的優化

至此流式分表獲取資料的原理基本上就是這樣,針對這種情況下我們該如何進行對分頁資料進行優化,因為上圖資料庫模組內部的區域是未知的也就是說我們是不知道索引“1”後面的索引“2”和其他語句下的當前索引大小情況,我們只知道索引“1”和索引“2”在本張表裡面的排序情況,

針對這種情況我們應該是沒辦法進行程式的優化了,可以理解為目前情況下已經是最優解了。但是如果我們仔細一想可以發現事情並不簡單

大家能看懂嗎我們只需要讓程式的獲取方式按順序那麼就可以保證效能最佳 O(1),所以針對時間分表或者順序分表的情況下我們一般情況下使用時間倒序或者順序,那麼就可以告訴程式如何排序,又可以得知,在對應順序的情況下每張表都是順序的又因為只要保證如下就可以了

有些朋友可能會有疑問,為什麼order by id也可以這樣,其實order by id是不可以這樣的,但是如果你這樣又會怎麼樣?難道資料庫用它最優解排序返回是正確,程式用最優解排序返回就不是正確了?

sharding-core的優化升階

可能有些噴友認為優化到這裡就是差不多了但是其實sharding-core針對優化還不止如此,

因為這種排序需要讓程式知道以某種情況排序可以按表順序排序達到效能最優,但是如果我是Id取模或者範圍就會導致這個排序僅僅只適合id排序如果需要按別的來排序就沒辦法了還是得走流式分表.

那麼該如何優化呢還是一樣我們忽略了分頁是2步操作

這種排序僅僅需要的是第一存在order by 第二告訴系統skip多少後需要啟用反排,並且該情況適用於任何的分表規則id取模或者別的其他情況都是可以支援的

你以為sharding-core的優化結束了嗎?

sharding-core已經實現了以上所有的解決方案,並且已經在實現第三種優化,就是極不規則情況下的分頁,具體就是當表查詢坐落到3張表後其中2張表或者1張表的count極少的情況下直接取到記憶體然後剩餘的1張表可以直接通過skip+take獲取資料後記憶體排序,

因為時間原因目前還沒實現後續會針對這個情況進行實現。

以上就是我為大家帶來的理論和乾貨,

具體的理論聽得爽了乾貨我再發一遍吧 sharding-core

sharding-core如何啟用高效能分頁

高效能分頁

sharding-core本身使用流式處理獲取資料在普通情況下和單表的差距基本沒有,但是在分頁跳過X頁後,效能會隨著X的增大而減小O(n)

目前該框架已經實現了一套高效能分頁可以根據使用者配置,實現分頁功能。

支援版本x.2.0.16+

1.如何開啟分頁配置 比如我們針對使用者月新表進行分頁配置,先實現IPaginationConfiguration<>介面,該介面是分頁配置介面


public class SysUserSalaryPaginationConfiguration:IPaginationConfiguration<SysUserSalary>
{
public void Configure(PaginationBuilder<SysUserSalary> builder)
{
builder.PaginationSequence(o => o.Id)
.UseTailCompare(Comparer<string>.Default)
.UseQueryMatch(PaginationMatchEnum.Owner | PaginationMatchEnum.Named | PaginationMatchEnum.PrimaryMatch);
builder.PaginationSequence(o => o.DateOfMonth)
.UseQueryMatch(PaginationMatchEnum.Owner | PaginationMatchEnum.Named | PaginationMatchEnum.PrimaryMatch).UseAppendIfOrderNone(10);
builder.PaginationSequence(o => o.Salary)
.UseQueryMatch(PaginationMatchEnum.Owner | PaginationMatchEnum.Named | PaginationMatchEnum.PrimaryMatch).UseAppendIfOrderNone();
builder.ConfigReverseShardingPage(0.5d,10000L);
}
}

2.新增配置

在對應的使用者月薪路由中新增配置

        public override IPaginationConfiguration<SysUserSalary> CreatePaginationConfiguration()
{
return new SysUserSalaryPaginationConfiguration();
}

3.Configure內部為什麼意思?

  1. builder.PaginationSequence(o => o.Id) 配置當分頁orderby 欄位為Id時那麼分表所對應的表結構為順序,順序的規則通過UseTailCompare來設定,其中string為表tail,

    具體什麼意思就是說如果本次分頁設計3張表分別是table1,table2,table3,如果我沒配置id的情況下那麼需要查詢3張表然後分別進行流式聚合,如果我配置了id的情況下,如果本次sql查詢帶上了id作為order by欄位

    那麼就不需要分別查詢3張表,可以直接查詢table1如果table1的count大於你要跳過的頁數,假設分頁查詢先查詢多少條,table1:100條,table2:200條,table3:300條

    如果你要跳過90條獲取10條原先的時間就是O(100)現在的時間就是O(10)因為table1跳過了90條還剩餘10條;
  2. UseQueryMatch是什麼意思,這個就是表示你要匹配的規則,是必須是當前這個類下的屬性還是說只需要排序名稱一樣即可,因為有可能select new{}匿名物件型別就會不一樣,PrimaryMatch表示是否只需要第一個主要的

    orderby匹配上就行了,UseAppendIfOrderNone表示是否需要開啟在沒有對應order查詢條件的前提下新增本屬性排序,這樣可以保證順序排序效能最優
  3. builder.ConfigReverseShardingPage 表示是否需要啟用反向排序,因為正向排序在skip過多後會導致需要跳過的資料過多,尤其是最後幾頁,如果開啟其實最後幾頁就是前幾頁的反向排序,其中第一個引數表示跳過的因子,就是說

    skip必須大於分頁總total*該因子(0-1的double),第二個引數表示最少需要total多少條必須同時滿足兩個條件才會開啟(必須大於500),並且反向排序優先順序低於順序排序,

    4.如何使用
var shardingPageResultAsync = await _defaultTableDbContext.Set<SysUserMod>().OrderBy(o=>o.Age).ToShardingPageAsync(pageIndex, pageSize);

注意:如果你是按時間排序無論何種排序建議開啟並且加上時間順序排序,如果你是取模或者自定義分表,建議將Id作為順序排序,如果沒有特殊情況請使用id排序並且加上反向排序作為效能優化

測試

首先我們使用 EFCore.BulkExtensions

針對資料進行建立

一共近295.5w資料耗時24.2秒其中解析表路由耗時3.4秒,插入到本地20.8秒,實際300w訂單肯定要比這個時間長因為測試原因所以建立的訂單表字段比較少

再不起用高效能分表的情況下我們看下

流式分頁



基本在skip 1w後還是可以保持在500ms,skip2w後雖然記憶體波動不大但是基本上耗時也有顯著增加那麼如果開啟了高效能分表呢

高效能分頁







直接爆殺有沒有

如果需要使用請在nuget安裝ShardingCore記得勾選預覽版本哦安裝最新版

最後的最後

如果本文章對您有幫助請點下推薦,如果本框架對您有幫助請點下start,Thanks♪(・ω・)ノ github sharding-core