菜菜呀,咱们业务 BJKJ 有个表数据需要做迁移,现在有多少数据?
更多精彩内容请看 web 前端中文站
http://www.lisa33xiaoq.net 可按 Ctrl + D 进行收藏
大约 21 亿吧,2017 年以前的数据没有业务意义了,给你半天时间把这个事搞定,绩效给
1.? 数据库采用 Sqlserver 2008 R2,单表数据量 21 亿
数据迁移工作包括三个个步骤:
1.? 从源数据表查询出要迁移的数据
2.? 把数据插入新表
3.? 把旧表的数据删除
1.? 从源数据表分页获取数据,具体分页条数,太少则查询原表太频繁,太多则查询太慢。
SQL 语句类似于
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
3.? 把数据删除,其实这里删除还是有一个小难点,表没有标示列。这里不展开,因为这不是菜菜要说的重点。
????????如果你的数据量不大,以上方法完全没有问题,但是在 9 亿这个数字前面,以上方法显得心有余而力不足。一个字:慢,太慢,非常慢。
可以大体算一下,假如每秒可以迁移1000 条数据,大约需要的时间为(单位:分)
1.? 在 9 亿数据前查询必须命中索引,就算是非聚集索引菜菜也不推荐,首推聚集索引。
2.??如果你了解索引的原理,你应该明白,不停的插入新数据的时候,索引在不停的更新,调整,以保持树的平衡等特性。尤其是聚集索引影响甚大,因为还需要移动实际的数据。
提取以上两点共同的要素,那就是聚集索引。相应的解决方案也就应运而生:
1.??按照聚集索引分页查询数据
2.? 批量插入数据迎合聚集索引,即:按照聚集索引的顺序批量插入。
3. 按照聚集索引顺序批量删除
2.? 存储在硬盘中的每个文件都可分为两部分:文件头和存储数据的数据区。文件头用来记录文件名、文件属性、占用簇号等信息,文件头保存在一个簇并映射在 FAT 表(文件分配表)中。而真实的数据则是保存在数据区当中的。平常所做的删除,其实是修改文件头的前 2 个代码,这种修改映射在 FAT 表中,就为文件作了删除标记,并将文件所占簇号在 FAT 表中的登记项清零,表示释放空间,这也就是平常删除文件后,硬盘空间增大的原因。而真正的文件内容仍保存在数据区中,并未得以删除。要等到以后的数据写入,把此数据区覆盖掉,这样才算是彻底把原来的数据删除。如果不被后来保存的数据覆盖,它就不会从磁盘上抹掉。
???DateTime?dtMax?=?DateTime.Parse("2017.1.1");
???var?allClassId?=?DBProxy.GeSourcetLstClassId(dtMax)?.OrderBy(s=>s);
???D?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;
}
}
?1915560 / 60=31926 条/秒
?cpu 情况(不高):
1. 源数据库和目标数据库硬盘为 ssd,并且分别为不同的服务器
2. 迁移程序和数据库在同一个局域网,保障数据传输时候带宽不会成为瓶颈
3. 合理的设置 SqlBulkCopy 参数
4. 菜菜的场景大多数场景下每次批量插入的数据量达不到设置的值,因为有的 class_id 对应的数据量就几十条,甚至几条而已,打开关闭数据库连接也是需要耗时的
5. 单纯的批量添加或者批量删除操作
【注:本文源自网络文章资源,由站长整理发布】