protected
void
Button1_Click(
object
sender, EventArgs e)
{
DateTime beginTime = DateTime.Now;
Response.Write(
"开始时间:"
+ beginTime.ToString(
"yyyy年MM月dd日:HH:mm:ss:fff"
));
//构造一个Datatable存储将要批量导入的数据
DataTable dt =
new
DataTable();
dt.Columns.Add(
"id"
,
typeof
(
string
));
dt.Columns.Add(
"name"
,
typeof
(
string
));
// 见识下SqlBulkCopy强悍之处,来个十万条数数据试验
int
i;
for
(i = 0; i < 100000; i++)
{
DataRow dr = dt.NewRow();
dr[
"name"
] = i.ToString();
dt.Rows.Add(dr);
}
string
str = ConfigurationManager.ConnectionStrings[
"connStr"
].ConnectionString.ToString();
//声明数据库连接
SqlConnection conn =
new
SqlConnection(str);
conn.Open();
//声明SqlBulkCopy ,using释放非托管资源
using
(SqlBulkCopy sqlBC =
new
SqlBulkCopy(conn))
{
//一次批量的插入的数据量
sqlBC.BatchSize = 1000;
//超时之前操作完成所允许的秒数,如果超时则事务不会提交 ,数据将回滚,所有已复制的行都会从目标表中移除
sqlBC.BulkCopyTimeout = 60;
//設定 NotifyAfter 属性,以便在每插入10000 条数据时,呼叫相应事件。
sqlBC.NotifyAfter = 10000;
sqlBC.SqlRowsCopied +=
new
SqlRowsCopiedEventHandler(OnSqlRowsCopied);
//设置要批量写入的表
sqlBC.DestinationTableName =
"dbo.text"
;
//自定义的datatable和数据库的字段进行对应
sqlBC.ColumnMappings.Add(
"id"
,
"tel"
);
sqlBC.ColumnMappings.Add(
"name"
,
"neirong"
);
//批量写入
sqlBC.WriteToServer(dt);
}
conn.Dispose();
Response.Write(
"<br/>"
);
DateTime endTime = DateTime.Now;
Response.Write(
"结束时间:"
+ endTime.ToString(
"yyyy年MM月dd日:HH:mm:ss:fff"
));
TimeSpan useTime = endTime-beginTime;
//使用时间
Response.Write(
"<br/>插入时间:"
+ useTime.TotalSeconds.ToString()+
"秒"
);
}
//响应时事件
void
OnSqlRowsCopied(
object
sender, SqlRowsCopiedEventArgs e)
{
Response.Write(
"<br/> OK! "
);
}
----------------------------------------------------------------
在做大批量数据插入的时候,如果用Insert into ... values (...)这种方式的话效率极低,这里介绍两种性能比较好的批量插入方法。
1. 使用SqlBulkCopy
private static long SqlBulkCopyInsert()
{
Stopwatch stopwatch = new Stopwatch();
stopwatch.Start();
DataTable dataTable = GetTableSchema();
string passportKey;
for (int i = 0; i < count; i++)
{
passportKey = Guid.NewGuid().ToString();
DataRow dataRow = dataTable.NewRow();
dataRow[0] = passportKey;
dataTable.Rows.Add(dataRow);
}
SqlBulkCopy sqlBulkCopy = new SqlBulkCopy(connectionString);
sqlBulkCopy.DestinationTableName = "Passport";
sqlBulkCopy.BatchSize = dataTable.Rows.Count;
SqlConnection sqlConnection = new SqlConnection(connectionString);
sqlConnection.Open();
if (dataTable!=null && dataTable.Rows.Count!=0)
{
sqlBulkCopy.WriteToServer(dataTable);
}
sqlBulkCopy.Close();
sqlConnection.Close();
stopwatch.Stop();
return stopwatch.ElapsedMilliseconds;
}
使用SqlBulkCopy类进行数据插入其原理是采用了SQL Server的BCP协议进行数据的批量复制。这里我们先要建好一个DataTable(最好是通过DataAdapter来灌数据得到,因为这样出来的DataTable就已经有跟数据表相同的列定义,可以免去之后Mapping Column的步骤),把要插入的数据加进这个DataTable中,然后用SqlBulkCopy的实例来插入到数据库中。经过测试,SqlBulkCopy方法比直接用Sql语句插入数据的效率高出将近25倍。
另外批量导入SQL、MYSQL等数据是同样的for循环,使用拼出来的sql或者使用参数的方式传递或者使用事务等不同方式的传递效率都不同。如果不使用SqlBulkCopy的方式的话,我测试下来做快递是用一次事务来操作为最快。因为10000次的循环如果是每次提交,那么都有链接和停止数据库的操作,或者说他包含了1000次的小事务处理。如果外面就一个事务的话效率肯定会高。
using(SqlBulkCopy sbc = new SqlBulkCopy(connString))
{
try
{
sbc.DestinationTableName =ObjectName;
sbc.WriteToServer(dt);
}
catch(Exception ex)
{
throw new DataAccessException("BulkInsert调用异常", ex);
}
}
-----------------------------------------------------
最近做的项目由于之前的设计人员懒省事,不按照范式来,将一张表的扩展信息存到了一个“键-值”表中。如下图:
对于主表中的每一条信息,大约有60个“key”,也就是说主表中每插入1条记录,子表中必须要插入60条。
通过预估我们确定主表中最终的数据量大约是20万,也就是说,子表中会有20x60=1200万条记录。同样类型的“主-子”表我们一共有4对,且不说这些表的查询效率,单是每天一次的数据导入对于我们来说就是一项巨大的挑战。
在此我吐槽一下,本来一个十万级的数据库,就是让这种垃圾“设计师”生生给搞成了个千万级的。而且最初他提出的数据插入方案是将每一条数据都生成一条Insert语句,然后逐条调用ExecuteNoQuery执行,后果就是测试用的3000条主表记录,共生成3000x60x4=72万条数据,花费7小时执行完毕,性能30条/秒。后来他辞职了,换了个人,然后第二任也辞职了,第三任就是在下。
项目到我手里之后,在我的坚持下重新进行了软件结构设计,由于数据库系统已经被另一个子系统使用,所以没办法更改了,只好去寻找一种高效的插入方式。
最开始我使用多线程,开10个线程,使性能提升到300条/秒,测试用记录花费大约40分钟插入完毕,对于多60倍的正式数据来说,40小时执行完毕显然不能满足我们每天一次数据导入工作的要求。
通过Google大神,我找到了SqlBulkCopy。
经过测试,性能我很满意,4000条/秒,那就先用它吧,下一阶段的工作重点就是干掉“键-值”表。
在使用中,我也碰到了一些“莫名其妙”的问题,在此记下,以备查询。