l--*_*''' 11 c# sql sql-server
我在C#上逐行写入SQL服务器上的两个表.
我的C#app将参数传递给2个存储过程,每个过程都将行插入表中.
每次调用存储过程时,我都会打开然后关闭连接.
我需要在数据库中写入大约100米的行.
每次调用存储过程时,我应该关闭并打开连接吗?
这是我正在做的一个例子:
public static void Insert_TestResults(TestResults testresults)
{
try
{
DbConnection cn = GetConnection2();
cn.Open();
// stored procedure
DbCommand cmd = GetStoredProcCommand(cn, "Insert_TestResults");
DbParameter param;
param = CreateInParameter("TestName", DbType.String);
param.Value = testresults.TestName;
cmd.Parameters.Add(param);
if (testresults.Result != -9999999999M)
{
param = CreateInParameter("Result", DbType.Decimal);
param.Value = testresults.Result;
cmd.Parameters.Add(param);
}
param = CreateInParameter("NonNumericResult", DbType.String);
param.Value = testresults.NonNumericResult;
cmd.Parameters.Add(param);
param = CreateInParameter("QuickLabDumpID", DbType.Int32);
param.Value = testresults.QuickLabDumpID;
cmd.Parameters.Add(param);
// execute
cmd.ExecuteNonQuery();
if (cn.State == ConnectionState.Open)
cn.Close();
}
catch (Exception e)
{
throw e;
}
}
Run Code Online (Sandbox Code Playgroud)
这是服务器上的存储过程:
USE [SalesDWH]
GO
/****** Object: StoredProcedure [dbo].[Insert_TestResults] Script Date: 12/26/2011 10:45:08 ******/
SET ANSI_NULLS ON
GO
SET QUOTED_IDENTIFIER ON
GO
-- =============================================
-- Author: <Author,,Name>
-- Create date: <Create Date,,>
-- Description: <Description,,>
-- =============================================
ALTER PROCEDURE [dbo].[Insert_TestResults]
-- Add the parameters for the stored procedure here
@TestName varchar (500),
@Result decimal (18,4)=null,
@NonNumericResult varchar (50)=null,
@QuickLabDumpid int
AS
BEGIN
-- SET NOCOUNT ON added to prevent extra result sets from
-- interfering with SELECT statements.
SET NOCOUNT ON;
INSERT INTO [SalesDWH].[dbo].[TestResults]
([TestName]
,[Result]
,nonnumericresult
,[QuickLabDumpid])
VALUES
(@TestName,@Result,@nonnumericresult,@QuickLabDumpID)
END
Run Code Online (Sandbox Code Playgroud)
大约100米行需要3天.这对我来说似乎太慢了.我该怎么做才能加快速度呢?关于打开/关闭连接的标准有多少次?
kuu*_*nbo 17
还有一个选择..NET Framework 自2.0以来就有了SqlBulkCopy类.您必须要做的主要是确保DataTable架构与您的表匹配.在您的测试用例中,类似这样的事情:
private void _initDataTable() {
dt = new DataTable();
dt.Columns.Add(new DataColumn() {
DataType = Type.GetType("System.String"),
ColumnName = "TestName"
});
dt.Columns.Add(new DataColumn() {
DataType = Type.GetType("System.Decimal"),
ColumnName = "Result"
});
dt.Columns.Add(new DataColumn() {
DataType = Type.GetType("System.String"),
ColumnName = "NonNumericResult"
});
dt.Columns.Add(new DataColumn() {
DataType = Type.GetType("System.Int32"),
ColumnName = "QuickLabDumpid"
});
}
Run Code Online (Sandbox Code Playgroud)
数据访问代码如下所示:
private void _insertData() {
using (var c = new SqlConnection(CS)) {
c.Open();
using (var trans = c.BeginTransaction()) {
try {
using (var bc = new SqlBulkCopy(
c, SqlBulkCopyOptions.TableLock, trans))
{
bc.DestinationTableName = "dbo.Insert_TestResults";
bc.WriteToServer(dt);
}
trans.Commit();
}
catch (Exception e) {
trans.Rollback();
throw;
}
}
}
}
Run Code Online (Sandbox Code Playgroud)
测试了这样的1000万条记录:
private void _fillDataTable() {
int batchToInsert = 1000000;
int numberOfTimes = 10;
int recordCounter = 1;
for (int i = 0; i < numberOfTimes; ++i) {
for (int j = 0; j < batchToInsert; j++) {
var row = dt.NewRow();
row[0] = string.Format("TestName{0}", recordCounter);
row[1] = (decimal) i;
row[2] = string.Format("NonNumericResult{0}", recordCounter);
row[3] = i;
dt.Rows.Add(row);
recordCounter += 1;
}
_insertData();
dt.Clear();
}
}
Run Code Online (Sandbox Code Playgroud)
我的开发机器花了两分半钟的时间.您可能想要尝试一次批量处理多少条记录.(不像上面的测试案例那样100万)显然你把这个数据量的10倍以上放入表中(猜测你的实时数据会更大),但我非常怀疑这个方法会不会3天 :)
无论你决定采用什么方法,祝你好运.
编辑:如果不明显,我忘了提 - 因为你在设置DestinationTableName属性时指定了表名,这就是你需要的 - 没有存储过程或任何其他SQL语句.
I a*_*ica 13
如果您使用的是SQL Server 2008,则可以通过表值参数一次发送多条记录:
create type testResultUpload as table
(
TestName varchar(500),
Result decimal(18,4) null,
NonNumericResult varchar(50) null,
QuickLabDumpid int
)
Run Code Online (Sandbox Code Playgroud)
然后你可以在客户端建立一个DataTable并将其作为一个块传递给sql.虽然,你可能想要一次做一千个开始.
从参数定义开始,您必须修改存储过程以处理输入记录集
alter proc Insert_TestResult
(
@testResultUpload testResultUpload readonly -- tvp must be readonly
)
as begin
-- This is short and sweet for demonstrative purposes
-- but you should explicitly list your columns
insert [SalesDWH].[dbo].[TestResults]
select
*
from @testResultImport
end
Run Code Online (Sandbox Code Playgroud)
然后在您的客户端:
// create your datatable in the form of the newly created sql type
var dt = new DataTable();
dt.Columns.Add("TestName", typeof(String));
dt.Columns.Add("Result", typeof(Decimal));
dt.Columns.Add("NonNumericResult", typeof(String));
dt.Columns.Add("QuickLabDumpid", typeof(String));
// add your rows here (maybe do it in steps of a thousand
// 100 Million over the pipe at once is ill-advised)
// call the following code to hit sql
using (var cnx = new SqlConnection("your connection string"))
using (var cmd = new SqlCommand {
Connection = cnx,
CommandType = CommandType.StoredProcedure,
CommandText = "dbo.Insert_TestResults",
Parameters = {
new SqlParameter {
ParameterName = "@testResultUpload",
Value = dt,
SqlDbType = SqlDbType.Structured // make sure to specify structured
}
}
})
{
cnx.Open();
cmd.ExecuteNonQuery();
}
Run Code Online (Sandbox Code Playgroud)
您无需为每个请求打开连接.您可以在开始时打开它一次,并在完成后关闭它.但是,启用连接池(默认情况下),打开和关闭连接并不是一个昂贵的过程.
你的程序很慢,主要是因为:
第一个修复是将插入分组到事务中 - 每个事务可能有1000行或类似的事情.
第二种方法的修复方法是使用命令批处理(一次发送多个命令,用分号分隔)或表值参数.TVP也很好,因为INSERT INTO SELECT FROM命令作为单个事务执行.
可实现的插入速度也受到日志磁盘速度的限制.确保DB日志位于与DB数据分开的磁盘上.确保日志碎片化并预先生长到您需要的大小也会有所帮助.
使用SqlBulkCopy是另一种选择,它还可以帮助最小化数据库日志的负载,具体取决于它的配置方式.
此外,如果您同时插入100M行,则可以考虑在启动之前删除表中的任何索引,并在完成后重新添加它们.否则,如果您不按聚簇索引的顺序插入行,它将非常快速地分段,对于非聚簇索引,您基本上是插入到主表中的每个插入的附加表中 - 在顶部碎片问题.