我在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;
}
}
以下是服务器上的存储过程:
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
对于大约100米行,需要3天。这对我来说似乎太慢了。我该怎么做才能加快速度呢?关于打开/关闭连接的标准有多少次?
答案 0 :(得分:16)
还有一个选择。自2.0以来,.NET Framework已经拥有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"
});
}
数据访问代码如下所示:
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;
}
}
}
}
使用这样的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();
}
}
我的开发机器花了两分半钟。您可能想要尝试一次批量处理多少条记录。 (不像上面的测试用例那样100万)显然你将这些数据量的10倍以上放入表中(猜测你的实时数据会更大),但我非常怀疑这种方法需要3天时间:))
祝你选择任何方法都好运。
修改强>:
如果它不明显,我忘了提及 - 因为您在设置DestinationTableName
属性时指定了表名,这就是您所需要的 - 没有存储过程或任何其他SQL语句。
答案 1 :(得分:13)
如果您使用的是SQL Server 2008,则可以通过表值参数一次发送多条记录:
create type testResultUpload as table
(
TestName varchar(500),
Result decimal(18,4) null,
NonNumericResult varchar(50) null,
QuickLabDumpid int
)
然后你可以在客户端建立一个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
然后在您的客户端:
// 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();
}
答案 2 :(得分:6)
您无需为每个请求打开连接。您可以在开始时打开它一次,并在完成后关闭它。但是,启用连接池(默认情况下),打开和关闭连接并不是一个昂贵的过程。
你的程序很慢,主要是因为:
第一个修复是将插入分组到事务中 - 每个事务可能有1000行或类似的事情。
第二种方法的修复方法是使用命令批处理(一次发送多个命令,用分号分隔)或表值参数。 TVP也很好,因为INSERT INTO SELECT FROM
命令作为单个事务执行。
可实现的插入速度也受到日志磁盘速度的限制。确保DB日志位于与DB数据分开的磁盘上。确保日志碎片化并预先生长到您需要的大小也会有所帮助。
使用SqlBulkCopy
是另一种选择,它还可以帮助最小化数据库日志的负载,具体取决于它的配置方式。
此外,如果您同时插入100M行,则可以考虑在启动之前删除表中的任何索引,并在完成后重新添加它们。否则,如果您不按聚簇索引的顺序插入行,它将非常快速地分段,对于非聚簇索引,您基本上是插入到主表中的每个插入的附加表中 - 在顶部碎片问题。
答案 3 :(得分:2)
无需在每次sp执行之间关闭和重新打开。此外,您可以通过将工作分成块并在单独的线程上执行每个块来减少总体执行时间,因此代替100m顺序调用,10个线程同时进行10m调用;每个线程一个连接:打开,执行循环,关闭。
答案 4 :(得分:1)
如果它是多用户,那么一旦你完成它们就关闭连接,如果单个用户那么你可以在应用的一生中保持打开:)
答案 5 :(得分:1)
为什么不使用与Idisposable的连接。在这种情况下,您无需实际关闭,但它会自动返回连接池。请参考这个,你会更了解我的意思。如果您有多线程应用程序,请使用这种方式。 http://msdn.microsoft.com/en-us/library/8xx3tyca(v=vs.80).aspx
答案 6 :(得分:1)
只要在每次调用GetConnection2
时都使用相同的连接字符串,每次打开/关闭不的SQL Server物理连接 - .NET会保持连接打开和重用。它仍会导致一些性能损失,但不会像重新连接到数据库那么大。
答案 7 :(得分:1)
连接被缓存,因此重新打开它们很便宜。做什么创建最可维护的代码。
答案 8 :(得分:1)
您应该使用connection pooling,在web.config上正常设置,这样每次打开和关闭连接时都不会实际打开和关闭,而是从池中选择。还可以使用using
确保正确处理连接。
答案 9 :(得分:1)
SQL已被构思和优化以处理记录集。如果使用循环在程序上工作,SQL将表现不佳。
我不知道这是否适用于您的情况,但请尝试使用INSERT-INTO-SELECT-FROM语句。
关闭连接是一个很好的做法,如果你不知道需要多长时间,直到你执行下一个命令,但是如果在循环中执行一堆命令,我就不会关闭并重新打开每次连接。