通过几分钟的排查,数据库状况以下:服务器
至于汇集索引和非汇集索引等知识,请各位移步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状况(不高):
磁盘队列状况(不高):
在如下状况下速度还将提升