通過幾分鐘的排查,數據庫狀況以下:服務器
至於彙集索引和非彙集索引等知識,請各位移步google或者百度。架構
至於業務,不是太複雜。通過相關人員諮詢,大約40%的請求爲單條Insert,大約60%的請求爲按class_id 和in_time(倒序)分頁獲取數據。Select請求所有命中彙集索引,因此性能很是高。這也是彙集索引之因此這樣設計的目的。 app
因爲單表數據量已經超過21億,而且2017年之前的數據幾乎不影響業務,因此決定把2017年之前(不包括2017年)的數據遷移到新表,僅供之後特殊業務查詢使用。通過查詢大約有9億數據量。
數據遷移工做包括三個個步驟:ide
這裏申明一點,就算是傳統的作法也須要分頁獲取源數據,由於你的內存一次性裝載不下9億條數據。性能
SELECT * FROM ( SELECT *,ROW_NUMBER() OVER(ORDER BY class_id,in_time) p FROM tablexx WHERE in_time <'2017.1.1' ) t WHERE t.p BETWEEN 1 AND 100
若是你的數據量不大,以上方法徹底沒有問題,可是在9億這個數字前面,以上方法顯得愛莫能助。一個字:慢,太慢,很是慢。google
能夠大致算一下,假如每秒能夠遷移1000條數據,大約須要的時間爲(單位:分)設計
900000000/1000/60=15000(分鐘)code
大約須要10天^ V ^server
以上的傳統作法弊端在哪裏呢?
提取以上兩點共同的要素,那就是彙集索引。相應的解決方案也就應運而生:
因爲作了表分區,若是有一種方式把2017年之前的分區直接在磁盤物理層面從當前表剝離,而後掛載到另一個表,可算是神級操做。有誰能指導一下菜菜,感激涕零
一個表的彙集索引的順序就是實際數據文件的順序,映射到磁盤上,本質上位於同一個磁道上,因此操做的時候磁盤的磁頭沒必要跳躍着去操做。
DateTime dtMax = DateTime.Parse("2017.1.1"); var allClassId = DBProxy.GeSourcetLstClassId(dtMax)?.OrderBy(s=>s);
按照第一步class_id 列表順序查詢數據,每一個class_id 分頁獲取,而後插入目標表,所有完成而後刪除源表相應class_id的數據。(所有命中彙集索引)
int pageIndex = 1; //頁碼 int pageCount = 20000;//每頁的數據條數 DataTable tempData =null; int successCount = 0; foreach (var classId in allClassId) { tempData = null; pageIndex = 1; while (true) { int startIndex = (pageIndex - 1) * pageCount+1; int endIndex = pageIndex * pageCount; tempData = DBProxy.GetSourceDataByClassIdTable(dtMax, classId, startIndex, endIndex); if (tempData == null || tempData.Rows.Count==0) { //最後一頁無數據了,刪除源數據源數據而後跳出 DBProxy.DeleteSourceClassData(dtMax, classId); break; } else { DBProxy.AddTargetData(tempData); } pageIndex++; } successCount++; Console.WriteLine($"班級:{classId} 完成,已經完成:{successCount}個"); }
DBProxy 完整代碼:
class DBProxy { //獲取要遷移的數據全部班級id public static IEnumerable<int> GeSourcetLstClassId(DateTime dtMax) { var connection = Config.GetConnection(Config.SourceDBStr); string Sql = @"SELECT class_id FROM tablexx WHERE in_time <@dtMax GROUP BY class_id "; using (connection) { return connection.Query<int>(Sql, new { dtMax = dtMax }, commandType: System.Data.CommandType.Text); } } public static DataTable GetSourceDataByClassIdTable(DateTime dtMax, int classId, int startIndex, int endIndex) { var connection = Config.GetConnection(Config.SourceDBStr); string Sql = @" SELECT * FROM ( SELECT *,ROW_NUMBER() OVER(ORDER BY in_time desc) p FROM tablexx WHERE in_time <@dtMax AND class_id=@classId ) t WHERE t.p BETWEEN @startIndex AND @endIndex "; using (connection) { DataTable table = new DataTable("MyTable"); var reader = connection.ExecuteReader(Sql, new { dtMax = dtMax, classId = classId, startIndex = startIndex, endIndex = endIndex }, commandType: System.Data.CommandType.Text); table.Load(reader); reader.Dispose(); return table; } } public static int DeleteSourceClassData(DateTime dtMax, int classId) { var connection = Config.GetConnection(Config.SourceDBStr); string Sql = @" delete from tablexx WHERE in_time <@dtMax AND class_id=@classId "; using (connection) { return connection.Execute(Sql, new { dtMax = dtMax, classId = classId }, commandType: System.Data.CommandType.Text); } } //SqlBulkCopy 批量添加數據 public static int AddTargetData(DataTable data) { var connection = Config.GetConnection(Config.TargetDBStr); using (var sbc = new SqlBulkCopy(connection)) { sbc.DestinationTableName = "tablexx_2017"; sbc.ColumnMappings.Add("class_id", "class_id"); sbc.ColumnMappings.Add("in_time", "in_time"); . . . using (connection) { connection.Open(); sbc.WriteToServer(data); } } return 1; } }
程序本機運行,開***鏈接遠程DB服務器,運行1分鐘,遷移的數據數據量爲 1915560,每秒約3萬條數據
1915560 / 60=31926 條/秒
cpu狀況(不高):
磁盤隊列狀況(不高):
在如下狀況下速度還將提升