SqlSugar分表

一、使用場景

 (1)可擴展架構設計,比如一個ERP用5年不卡,到了10就卡了因爲數據太多了,這個時候很多人都是備份然後清空數據

 (2) 數據量太多 ,例如每天都有 幾十上百萬的數據進入庫,如果不分表後面查詢將會非常緩慢

   (3)   性能瓶頸 ,數據庫現有數據超過1個億,很多情況下索引會莫名失效,性能大大下降

 

二、 內置分表使用

自帶分表支持按年、按季、按月、按周、按日進行分表(支持混合使用)

2.1 定義實體

我們定義一個實體,主鍵不能用自增或者int ,設爲long或者guid都可以,我例子就用自帶的雪花ID實現分表

[SplitTable(SplitType.Year)]//按年分表 (自帶分表支持 年、季、月、周、日)
[SugarTable("SplitTestTable_{year}{month}{day}")]//3個變量必須要有,這麼設計爲了兼容開始按年,後面改成按月、按日
 public class SplitTestTable
 {
     [SugarColumn(IsPrimaryKey =true)]
     public long Id { getset; }
  
     public string Name { getset; }
      
     [SugarColumn(IsNullable = true)]//設置爲可空字段 (更多用法看文檔 遷移)
     public DateTime UpdateTime{get;set;}
      
     [SplitField] //分表字段 在插入的時候會根據這個字段插入哪個表,在更新刪除的時候用這個字段找出相關表
     public DateTime CreateTime { getset; }
 } 
  
 //按年分表格式如下
 SplitTestTable_20220101
  
 //比如現在5月按月分表格式如下
 SplitTestTable_20220501
  
 //比如現在5月11日按日分表格式如下
 SplitTestTable_20220511
  
 //因爲插入會根據實體自動建表 
 //比如何設置可空類型等設置需要知道如何配置:https://www.donet5.com/Home/Doc?typeId=1206

2.2 同步表和結構

假如分了20張表,實體類發生變更,那麼 20張表可以自動同步結構,與實體一致

注意:插入會自動建表不需要這行代碼,主要用於實體改動後同步多個表結構,或者一張表沒有初始一張

禁止寫到業務裏面多次執行

//不寫這行代碼 你也可以用插入建表,插入用法看文檔下面
db.CodeFirst
    .SplitTables()//標識分表
     .InitTables<SplitTestTable>(); //程序啓動時加這一行,如果一張表沒有會初始化一張

2.3.1 查詢: 時間過濾

  通過開始時間和結束時間自動生成CreateTime的過濾並且找到對應時間的表

//簡單示例
var list=db.Queryable<OrderSpliteTest>().SplitTable(beginDate,endDate).ToPageList(1,2); 
 
//結合Where
var list=db.Queryable<OrderSpliteTest>().Where(it=>it.Id>0).SplitTable(beginDate,endDate).ToPageList(1,2); 
 
//注意:
//1、 分頁有 OrderBy寫 SplitTable 後面 ,uinon all後在排序
//2、 Where儘量寫到 SplitTable 前面,先過濾在union all 
//原理:(sql union sql2) 寫SplitTable 後面生成的括號外面,寫前生成的在括號裏面

2.3.2 查詢: 選擇最近的表

如果下面是按年分表 Take(3)  表示 只查 最近3年的 分表數據 

var list=db.Queryable<OrderSpliteTest>()
            .Where(it=>it.Pk==Guid.NewGuid())
            .SplitTable(tabs => tabs.Take(3))//近3張,也可以表達式選擇
            .ToList();

2.3.3 查詢: 精準定位一張表

根據分表字段的值可以精準的定位到具體是哪一個分表,比Take(N)性能要高出很多

var name=Db.SplitHelper<SplitTestTable>().GetTableName(data.CreateTime);//根據時間獲取表名
 
//推薦: 表不存在不會報錯
var list=db.Queryable<OrderSpliteTest>().SplitTable(tabs => tabs.InTableNames(name)).ToList()
 
//不推薦:查詢不推薦用,刪除和更新可以用
var list=Db.Queryable<SplitTestTable>().AS(name).Where(it => it.Id==data.Id).ToList();
//修改、刪除、更新都可以用As(Name)

2.3.4 查詢: 表達式定位哪幾張表

Db.Queryable<SplitTestTable>()
          .Where(it => it.Id==data.Id)
          .SplitTable(tas => tas.Where(y=>y.TableName.Contains("2019")))//表名包含2019的表
          .ToList();

2.3.5 查詢: 分表Join正常表 

(推薦插入存全,儘量不要聯表影響性能)

//分表使用聯表查詢
var list=db.Queryable<Order>() // Order是分表
.SplitTable(tabs=>tabs.Take(3)) //可以換成1-8的所有分表寫法,不是隻能take
.LeftJoin<Custom>((o,c)=>o.CustomId==c.Id)//Custom正常表
.Select((o,c)=>new { name=o.Name,cname=c.Name }).ToPageList(1,2); 

2.3.6 查詢: 分表JOIN分表 

(推薦插入存全,儘量不要聯表影響性能)

var rightQuery=db.Queryable<Custom>().SplitTable(tabs=>tabs.Take(3)) ;
var list=db.Queryable<Order>().SplitTable(tabs=>tabs.Take(3)) 
.LeftJoin<Custom>(rightQuery,(o,c)=>o.CustomId==c.Id) // Join  rightQuery
.Select((o,c)=>new { name=o.Name,cname=c.Name }).ToPageList(1,2); 
 
 
//技巧:如果是單表分表沒有表返回第一個表可以防止報錯  升級到:5.1.4.127 +
SplitTable(it=>it.ContainsTableNamesIfNullDefaultFirst("table"))

2.3.7 查詢: 性能優化

條件儘可能寫在SplitTable前面,因爲這樣會先過濾在合併

.Where(it => it.Pk == Guid.NewGuid()) //先過濾
.SplitTable(tabs => tabs.Take(3))//在分表

2.3.8 查詢: 所有分表檢索

沒辦法精確過濾表時用,Where一定要寫SplitTable前面

//如果是主鍵查詢哪怕100個分表都很快 
var list = db.Queryable<OrderSpliteTest>()
         .Where(it => it.Pk == Guid.NewGuid()) //適合有索引列,單條或者少量數據查詢
         .SplitTable().ToList();//沒有條件就是全部表
           
//老版本
var list = db.Queryable<OrderSpliteTest>()
         .Where(it => it.Pk == Guid.NewGuid()) //適合有索引列,單條或者少量數據查詢
          .SplitTable(tab=>tab).ToList();

 

2.4 插入 

因爲我們用的是Long所以採用雪花ID插入(guid也可以禁止使用自增列), 實體結構看上面 3.1

注意:.SplitTable不要漏掉了

var data = new SplitTestTable()
{
      CreateTime=Convert.ToDateTime("2019-12-1"),//要配置分表字段通過分表字段建表
      Name="jack"
 };
 //雪花ID+表不存在會建表
 db.Insertable(data).SplitTable().ExecuteReturnSnowflakeIdList();//插入並返回雪花ID並且自動賦值ID   
  //服務器時間修改、不同端口用同一個代碼、多個程序插入一個表都需要用到WorkId
  //保證WorkId唯一 ,程序啓動時配置 SnowFlakeSingle.WorkId=從配置文件讀取;
  
  
 //GUID+表不存在會建表
  db.Insertable(data).SplitTable().ExecuteCommand();//插入GUID 自動賦值 ID
   
 //大數據寫入+表不存在會建表
 db.Fastest<OrderSpliteTest>().SplitTable().BulkCopy(List<OrderSpliteTest>);//自動找表大數據寫入
  
 //不會自動建表 如果表100%存在用這個性能好些
 db.Fastest<OrderSpliteTest>().AS(表名).BulkCopy(List<OrderSpliteTest>);//固定表大數據寫入
  
  
 //大數據寫入方式如果用到雪花ID需要手動賦值:SnowFlakeSingle.Instance.NextId()
 //部分數據庫需配置 具體用法看文檔: https://www.donet5.com/Home/Doc?typeId=2404

批量插入 因爲我們是根據CreateTime進行的分表,生成的SQL語句如下:

var datas = new List<SplitTestTable>(){
new SplitTestTable(){CreateTime=Convert.ToDateTime("2019-12-1"),Name="jack"} ,
new SplitTestTable(){CreateTime=Convert.ToDateTime("2022-02-1"),Name="jack"},
new SplitTestTable(){CreateTime=Convert.ToDateTime("2020-02-1"),Name="jack"},
new SplitTestTable(){CreateTime=Convert.ToDateTime("2021-12-1"),Name="jack"}
};
 
db.Insertable(datas).SplitTable().ExecuteReturnSnowflakeIdList();//插入返回雪花ID集合

 執行完生成的表

生成的Sql: 

自動識別4條記錄,分別插入4個不同的表中  

2.5 刪除數據 

(1)推薦用法:新功能 5.0.7.7 preview及以上版本

 //直接根據實體集合刪除 (全自動 找表插入)
 db.Deleteable(deleteList).SplitTable().ExecuteCommand();//,SplitTable不能少

(2)最近3張表都執行一遍刪除

db.Deleteable<SplitTestTable>().In(id).SplitTable(tas=>tas.Take(3)).ExecuteCommand();

(3)精準刪除     

 相對於上面的操作性能更高,可以精準找到具體表

var tableName=Db.SplitHelper<SplitTestTable>().GetTableName(data.CreateTime);//根據時間獲取表名
db.Deleteable<SplitTestTable>().AS(tableName).Where(deldata).ExecuteCommand();
 //DELETE FROM [SplitTestTable_20210101] WHERE [Id] IN (1454676863531089920)

(4)範圍刪除

var tables = db.SplitHelper<OrderSpliteTest>().GetTables().Take(3);//近3張分表
foreach (var item in tables)
{
   //刪除1點到6點時間內數據
   db.Deleteable<OrderSpliteTest>() .AS(item.TableName)//使用當前分表名
                     .Where(it => it.Time.Hour < 1&&it.Time.Hour<6)
                    .ExecuteCommand();
}

 

2.6 更新數據    

推薦用法: 新功能 5.0.7.7 preview及以上版本 

//直接根據實體集合更新 (全自動 找表更新)
db.Updateable(updateList).SplitTable().ExecuteCommand();//.SplitTable()不能少
 
//BulkCopy分表更新
db.Fastest<OrderSpliteTest>().SplitTable().BulkUpdate(List<OrderSpliteTest>);
//部分數據庫需配置 具體用法看文檔: 
 
 
//範圍更新
var tables = db.SplitHelper<OrderSpliteTest>().GetTables().Take(3);//近3張分表
foreach (var item in tables)
{
   //更新1點到6點時間內數據
   db.Updateable<OrderSpliteTest>() .AS(item.TableName)//使用分表名
                    .SetColumns(it=>new OrderSpliteTest(){ Static=1 })
                    .Where(it => it.Time.Hour < 1&&it.Time.Hour<6)
                    .ExecuteCommand();
}

更多用法:

//更新近3張表
db.Updateable(deldata).SplitTable(tas=>tas.Take(3)).ExecuteCommand();
 
//精準找單個表
var tableName=Db.SplitHelper<SplitTestTable>().GetTableName(data.CreateTime);//根據時間獲取表名
db.Updateable(deldata).AS(tableName).ExecuteCommand();//實體
db.Updateable<TestEnity>().AS(tableName).SetColumns(..).Where(...).ExecuteCommand();//表達式
 
//通過表達式過濾出要更新的表
db.Updateable(deldata).SplitTable(tas => tas.Where(y=>y.TableName.Contains("_2019"))).ExecuteCommand();

2.7 輔助方法

ORM不是所有功能都支持分表,我們可以分表輔助方法實現

//用例1:獲取所有表表名 ,可以用於循環處理每個表
var tables=db.SplitHelper<Order>().GetTables();
 
 
//例2:根據分表字段的值獲取表名
var tableName = db.SplitHelper<Order>().GetTableName(DateTime.Now);//有重載
db.Updateable(data).AS(tableName).ExecuteCommand()
 
 
//例3:根據當前對象獲取表名,有這個功能就可以把List進行分類
var tableNames =db.SplitHelper(List<T> dataList)).GetTableNames();//根據實體集合獲取表名集合
var tableName =db.SplitHelper(T data).GetTableName();//根據實體獲取表名 
 
Order_20220101- [記錄1,記錄2 ....]
Order_20210101- [記錄1,記錄2 ...]
 
Db.Storageable(Order_20220101所有記錄集合).As("Order_20220101").ExecuteCommand()

2.8 唯一判段

唯一列要加上索引,這樣性能才能保證上百張表裏面也不會慢

if (!db.Queryable<OrderSpliteTest>().SplitTable().Any(it => it.Name==p)) 
{
  //唯一不存在
}

三、 自定義分表:按單詞

疑問: 自定義分表可以支持多個字段

答:可以的,只要你重寫的方法兼容多個字段就可以

上面的分表功能是我們自帶集成的,比如我想實現自定義的分表我該如何實現呢?

3.1 按首字母拼音分表

 我們就寫個按24個字母進行分表的小例子,來學習一下如何自定義分表

3.2  創建分表類

我們新建一個類繼承成ISplitTableService 接口 

 public interface ISplitTableService
 {
        //獲取表名用於 SplitTable tas 篩選
        List<SplitTableInfo> GetAllTables(ISqlSugarClient db,EntityInfo EntityInfo,List<DbTableInfo> tableInfos);
        //獲取默認表名
        string GetTableName(ISqlSugarClient db, EntityInfo EntityInfo);
        string GetTableName(ISqlSugarClient db, EntityInfo EntityInfo, SplitType type);
        //根據分表字段會值(下面函數獲取分表字段值)獲取表名
        string GetTableName(ISqlSugarClient db, EntityInfo entityInfo, SplitType splitType, object fieldValue);
        //獲取分表字段的值 (可以多字段返回一個組合值)
        object GetFieldValue(ISqlSugarClient db, EntityInfo entityInfo, SplitType splitType, object entityValue);
 }

3.3  使用自定義分表

創建一個WordSplitService.cs繼承ISplitTableService

用例下載: 

USplit.rar

 

//配置方式1:配置自定義分表
db.CurrentConnectionConfig.ConfigureExternalServices.SplitTableService =new WordSplitService();
 
//配置方式2:高版本支持了特性使用自定義分表 5.1.4.78
[SplitTable(SplitType._Custom01,typeof(WordSplitService))]
 
 
//插入數據
db.Insertable(new WordTestTable(){CreateTime=DateTime.Now,Name="BC"}).SplitTable().ExecuteReturnSnowflakeId();
db.Insertable(new WordTestTable(){CreateTime=DateTime.Now,Name="AC"}).SplitTable().ExecuteReturnSnowflakeId();
db.Insertable(new WordTestTable(){CreateTime=DateTime.Now,Name="ZBZ"}).SplitTable().ExecuteReturnSnowflakeId();  

 執行完數據庫就多了3張表,因爲是按首字母分的表 ,插入了3條記錄自動創建了3張表,插入生成的SQL:

INSERT INTO [WordTestTable_FirstB]
           ([Id],[Name],[CreateTime])
     VALUES
           (@Id,@Name,@CreateTime) ;
INSERT INTO [WordTestTable_FirstA]
           ([Id],[Name],[CreateTime])
     VALUES
           (@Id,@Name,@CreateTime) ;
INSERT INTO [WordTestTable_FirstZ]
           ([Id],[Name],[CreateTime])
     VALUES
           (@Id,@Name,@CreateTime) ;

查詢分表

//查詢字母A開頭的分
var listall = db.Queryable<WordTestTable>().Where(it => it.Name == "all").SplitTable(tas => tas.ContainsTableNames("_FirstA")).ToList();    
//生成的SQL:
//SELECT * FROM  (SELECT [Id],[Name],[CreateTime] FROM [WordTestTable_FirstA]  WHERE ( [Name] = @Name0UnionAll1 )) unionTable 

 

四、自定義分表2:按年月

支持2種方式配置自定義分表

//配置方式1:配置自定義分表
db.CurrentConnectionConfig.ConfigureExternalServices.SplitTableService =new yyyyMMService();
 
//配置方式2:高版本支持了特性使用自定義分表 5.1.4.78
[SplitTable(SplitType.Month,SplitType._Custom01,typeof(yyyyMMService))]

我們自帶的時間分表格式爲  xx_20220101這種,如果我想格式爲 xx_202201 那麼我們可以用自定義分表

查看:https://www.donet5.com/Ask/9/16110

注意:自定義的時間分表是沒辦法後期改變分表類型,比如你設置了按月那麼以後就不能改按周,如果想擴展性強用自帶的時間分表

 

五、性能優化

(1) 分表字段要建索引

(2) where寫到SplitTable前面

var list=db.Queryable<OrderSpliteTest>()
Where(it=>it.id>1)//where寫SplitTable前面
.SplitTable(beginDate,endDate).ToPageList(1,2)

(3) 如果分頁可以不查詢count,給個固定前10頁 (特別mysql查詢count是比較慢的)

六、視頻教程

https://www.bilibili.com/video/BV13B4y1h7Wu?p=4

 

七、時序數據庫

上面介紹的都是關係型數據庫對業務進行分表,如果使用時序數據庫 那麼數據庫會自動分表

並且使用和正常表一樣, 分表的事情都交給數據庫,當成正常表去用就行了

https://www.donet5.com/Home/Doc?typeId=2434

 

八、老表數據遷移分表

步驟1:

老表改名爲 xxxxxx ( 隨便什麼名字), 防止和分表的表名衝突引起錯誤

如果比較慢:你也可以改新建的名字 比如以前叫 order現在改 OrderNew

步驟2:

var pageList = new List<新類>();//主鍵不能是自增
var pageSize=200000;//每次讀取20萬 (數據庫是1-20萬 20萬-40萬這樣讀取)
db.Queryable<object>().As("xxxxxx").Select<新類>().ForEach(it =>{
                    pageList.Add(it);
                    if (pageList.Count==pageSize) //每個分頁的最一次執行,防止循操作庫 
                    {
                      db2.Fastest<新類>().SplitTable().BulkCopy(pageList);//插入分批數據
                      pageList = new List<新類>();//清空
                          
                    }
                },pageSize);//設置分頁 
 db2.Fastest<新類>().SplitTable().BulkCopy(pageList);//插入剩餘的 (最後一頁可能有不足20萬的)

 

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章