關於分庫分表方案詳細介紹
http://blog.csdn.net/bluishglc/article/details/7696085
這里就不作詳細描述了
分庫分表方案基本脫離不了這個結構,受制於實現的難度,好像沒有看到有很方便的實現方案框架
為了解決此問題,在CRL框架基礎上作了擴展,使CRL能很好實現此方案,以之前了解到的需求,基本能滿足了
本方案拆分結構表示為

會員為業務核心,所有業務圍繞會員來進行,所以垂直划分用會員編號作索引,將會員分配到不同的庫
會員訂單增長量是不固定的,所以需要平水拆分,和分庫一樣,一個表只存指定會員編號區間的訂單
了解基本需求,就可以制作方案了,以下主索引表示主數據編號
庫表結構配置
進行操作時,需要知道這個數據放在哪個庫,哪個表,因此需要把這個划分結構做成可配置,需要配置有:
- 數據庫:一共划分為幾個庫,主索引區間是多少
- 數據表:一共有幾個分表,每個分表容量是多少
- 數據表分表:屬於哪個表,主索引區間是多少
將結構以對象形式表示
DataBase 庫
/// <summary> /// 庫 /// 按主數據分垂直划分,將主數據按不同段存在不同庫中 /// </summary> public class DataBase:CRL.IModelBase { /// <summary> /// 庫名 /// </summary> public string Name { get; set; } /// <summary> /// 主數據開始INDEX /// </summary> public int MainDataStartIndex { get; set; } /// <summary> /// 主數據結束INDEX /// </summary> public int MainDataEndIndex { get; set; } /// <summary> /// 主數據表最大數據量 /// </summary> public int MaxMainDataTotal { get; set; } public override string ToString() { return string.Format("名稱:{0} 最大主數據量:{1} 索引開始:{2} 結束{3}", Name, MaxMainDataTotal, MainDataStartIndex, MainDataEndIndex); } }
Table 表
/// <summary> /// 表 /// 主數據表不分表,只按庫分,其它表再按主數據段分表 /// </summary> public class Table:CRL.IModelBase { /// <summary> /// 源表名 /// </summary> [CRL.Attribute.Field(FieldIndexType = Attribute.FieldIndexType.非聚集)] public string TableName { get; set; } /// <summary> /// 庫名 /// </summary> [CRL.Attribute.Field(FieldIndexType = Attribute.FieldIndexType.非聚集)] public string DataBaseName { get; set; } /// <summary> /// 分表數 /// </summary> public int TablePartTotal { get; set; } /// <summary> /// 分表最大數據量 /// </summary> public int MaxPartDataTotal { get; set; } /// <summary> /// 是否為主數據表 /// 主數據表在當前庫只存在一個 /// </summary> public bool IsMainTable { get; set; } }
TablePart 分表
/// <summary> /// 分表,主數據表不分表,只按庫分 /// 其它表按主數據段分 /// </summary> public class TablePart : CRL.IModelBase { /// <summary> /// 庫名 /// </summary> [CRL.Attribute.Field(FieldIndexType = Attribute.FieldIndexType.非聚集)] public string DataBaseName { get; set; } /// <summary> /// 源表名 /// </summary> [CRL.Attribute.Field(FieldIndexType = Attribute.FieldIndexType.非聚集)] public string TableName { get; set; } /// <summary> /// 分表名 /// </summary> public string PartName { get; set; } /// <summary> /// 分表索引,從0開始 /// </summary> public int PartIndex { get; set; } /// <summary> /// 主數據開始索引值 /// </summary> public int MainDataStartIndex { get; set; } /// <summary> /// 主數據結束索引值 /// </summary> public int MainDataEndIndex { get; set; } ///// <summary> ///// 分表最大數據量 ///// </summary> //public int MaxPartDataTotal //{ // get; // set; //} }
數據定位
通過以上配置,就可以按主索引進行表定位了
過程表示為:初始庫表配置=>按主索引定位庫=>再定位到表
/// <summary> /// 初始表 /// </summary> public static void Init() { _DataBase = DataBaseManage.Instance.QueryList(); _Table = TableManage.Instance.QueryList(); _TablePart = TablePartManage.Instance.QueryList(); } /// <summary> /// 按主數據索引,確定庫 /// </summary> /// <param name="mainDataIndex"></param> /// <returns></returns> public static DataBase GetDataBase(int mainDataIndex) { if (_DataBase.Count() == 0) { Init(); } var db = _DataBase.Find(b => mainDataIndex >= b.MainDataStartIndex && mainDataIndex <= b.MainDataEndIndex); if (db == null)//找屬於哪個庫 { throw new Exception("找不到指定的庫,在主數據索引:" + mainDataIndex); } return db; } /// <summary> /// 按主數據索引,獲取該查詢位置 /// </summary> /// <param name="tableName"></param> /// <param name="mainDataIndex"></param> /// <param name="db"></param> /// <returns></returns> public static Location GetLocation(string tableName, int mainDataIndex, DataBase db) { var table = _Table.Find(b => b.TableName == tableName && b.DataBaseName == db.Name); if (table == null)//找哪個表 { throw new Exception(string.Format("找不到指定的表{1}在庫{0}", db.Name, tableName)); } TablePart part; //找分表 if (table.IsMainTable)//如果只是主數據表,只按一個找就行了 { part = _TablePart.Find(b => b.TableName == tableName && b.DataBaseName == db.Name); } else//其它表,按分表找 { part = _TablePart.Find(b => mainDataIndex >= b.MainDataStartIndex && mainDataIndex <= b.MainDataEndIndex && b.TableName == tableName && b.DataBaseName == db.Name); } if (part == null) { throw new Exception(string.Format("找不到指定的表{1}在庫{0}", db.Name, tableName)); } return new Location() { DataBase = db, TablePart = part }; }
傳入主索引編號,調用定位方法,得到對應的庫和表名稱
var db=GetDataBase(100);//定位庫 var location=GetLocation("Order", 100, db);//定位表
這樣,庫表位置就有了,剩下的就是對數據進行操作了,不過這需要數據訪問框架能動態切換數據連接和映射對象,還好CRL能輕松實現
CRL內部調用比較復雜,這里不寫具體實現了,只列出關鍵方法,詳細實現可加入QQ群獲取最新源代碼
數據連接動態切換
因為CRL的數據訪問是在應用層委托實現的,很容易實現,本次升級后增加了分庫的區分
//配置數據連接 CRL.SettingConfig.GetDbAccess = (type) => { //可按type區分數據庫 if (type.ShardingDataBase != null) { if (type.ShardingDataBase.Name == "db1")//定位到DB1 { return WebTest.Code.LocalSqlHelper.TestConnection; } else { return WebTest.Code.LocalSqlHelper.TestConnection2;//定位到DB2 } } else { return WebTest.Code.LocalSqlHelper.TestConnection;//默認庫 } };
分表映射
在CRL內部實現了分表映射,如果傳入了定位,則按定位找到分表名
public static string GetTableName(string tableName, DbContext dbContext) { if (dbContext != null && dbContext.UseSharding) { if (dbContext.ShardingMainDataIndex == 0) { throw new Exception("未設置分表定位索引,dbContext.ShardingMainDataIndex"); } var location = Sharding.DBService.GetLocation(tableName, dbContext.ShardingMainDataIndex, dbContext.DBLocation.ShardingDataBase); tableName = location.TablePart.PartName; } return tableName; }
以上過程表示為

整體封裝
和之前CRL業務類封裝一樣,增加了CRL.Sharding.BaseProvider,繼承實現業務對象,就能實現分庫分表了
對比之前,此類刪除了一些無關方法,增加了SetLocation定位方法
以文檔帶的例子講解
會員實現
這里要注意主鍵的問題,不能為自增
public class MemberSharding : CRL.IModel { [CRL.Attribute.Field(KeepIdentity=true)]//保持插入主鍵 public int Id { get; set; } public string Name { get; set; } } public class MemberManage : CRL.Sharding.BaseProvider<MemberSharding> { public static MemberManage Instance { get { return new MemberManage(); } } }
訂單實現
public class OrderSharding : CRL.IModelBase { public int MemberId { get; set; } public string Remark { get; set; } } public class OrderManage : CRL.Sharding.BaseProvider<OrderSharding> { public static OrderManage Instance { get { return new OrderManage(); } } }
初始庫表配置
以下會創建兩個庫 db1,db2
db1會員編號為1~10 ,db2會員編號為 11~20 ,當插入會員編號小於11的數據,則會定位到db1,11到20則會定位到db2
訂單表OrderSharding設定為最大主數據容量5,1~5編號的會員訂單會放在OrderSharding,6~10則會放到OrderSharding_1
CRL.Sharding.DB.DataBaseManage.Instance.CleanData(); //創建庫分組 var db = new CRL.Sharding.DB.DataBase(); db.Name = "db1"; db.MaxMainDataTotal = 10; CRL.Sharding.DB.DataBaseManage.Instance.Create(db); CRL.Sharding.DB.DataBaseManage.Instance.Create(db); //創建表 var dbList = CRL.Sharding.DB.DataBaseManage.Instance.QueryList(); foreach(var item in dbList) { var table = new CRL.Sharding.DB.Table(); table.TableName = "MemberSharding"; table.IsMainTable = true; CRL.Sharding.DB.TableManage.Instance.Create(item, table, out error); var table2 = new CRL.Sharding.DB.Table(); table2.TableName = "OrderSharding"; table2.IsMainTable = false; table2.MaxPartDataTotal = 5; CRL.Sharding.DB.TableManage.Instance.Create(item, table2, out error); //創建分區 CRL.Sharding.DB.TablePartManage.Instance.Create(table2, out error); }
插入會員和訂單測試
插入的會員和訂單會按庫表配置分配到不同的表
這里為了演示,將定位信息保存了
var m = new Code.Sharding.MemberSharding(); m.Id = Convert.ToInt32(TextBox1.Text);//主索引編號 var location = CRL.Sharding.DBService.GetLocation("MemberSharding", m.Id); m.Name = location.ToString(); Code.Sharding.MemberManage.Instance.SetLocation(m.Id).Add(m); var order = new Code.Sharding.OrderSharding(); order.MemberId = m.Id; var location2 = CRL.Sharding.DBService.GetLocation("OrderSharding", m.Id); order.Remark = location2.ToString(); Code.Sharding.OrderManage.Instance.SetLocation(m.Id).Add(order); Label1.Text = "插入會員編號" + m.Id + "," + location + " 訂單" + location2;
數據查詢
通過主索引編號定位庫表,就能跟正常查詢一樣操作了
//會員查詢 var id = Convert.ToInt32(TextBox1.Text);//主索引編號 var list = Code.Sharding.OrderManage.Instance.SetLocation(id).QueryList(b => b.MemberId == id); GridView1.DataSource = list; GridView1.DataBind(); //訂單查詢 var id = Convert.ToInt32(TextBox1.Text);//主索引編號 var list = Code.Sharding.MemberManage.Instance.SetLocation(id).QueryList(b => b.Id == id); GridView1.DataSource = list; GridView1.DataBind();
結果演示如下

分表聯合查詢
查詢當前庫所有分表訂單union結果
設定union方式后,會遍歷分表生成union查詢
var id = Convert.ToInt32(TextBox1.Text); var orderManage = Code.Sharding.OrderManage.Instance.SetLocation(id); var query = orderManage.GetLambdaQuery(); query.UnionType = UnionType.UnionAll;//只需設定union方式即可 var list = orderManage.QueryList(query); GridView1.DataSource = list; GridView1.DataBind();
主索引的問題
以上演示主索引編號為手動輸入,實際業務是需要從索引表產生,CRL.Sharding也增加了索引獲取
CRL.Sharding.DB.DataSequenceManage.Instance.GetSequence();
分表結構創建
分表創建貌似比較麻煩,需要手動創建維護,然而不需要,CRL自動幫你創建了,這就是CRL強大之所在,在找不到表結構時,會按對象結構創建
分庫分表難點
庫表配置比較容易實現,比較麻煩的地方是從統一入口進行數據操作管理,要達到通用性,框架需要適應不同業務需求,封裝和繼承需要得到很好的支持
本方案已封裝在CRL2.4,在文檔示例中也有體現
演示地址:http://crl.changqidongli.com/page/Sharding.aspx
CRL最新版2.4代碼在QQ群可以得到,有其它想法歡迎討論,CRL3.0征集思路中,想共同開發的可與我聯系
QQ群:1582632 密語:CRL
CRL框架介紹和早期源碼:http://www.cnblogs.com/hubro/p/4616570.html
