C# 使用同一SqlConnection对SqlCommand.BeginExecutenQuery的多个并发调用
我有一些工作的C#代码,它使用SqlConnection创建临时表(例如#Foo),调用存储的进程来填充这些临时表并将结果返回给C#客户端,使用C#对这些结果执行复杂的计算,并使用计算结果更新先前创建的一个临时表 由于在整个过程中使用临时表,我们必须只有一个SqlConnection 在用计算结果更新temp表时,我发现了一个性能瓶颈。这段代码已经在批处理更新,以防止C#客户端内存不足。每批计算数据都通过SqlCommand.ExecuteNonQuery发送到存储过程,存储过程反过来更新临时表。代码大部分时间都在调用ExecuteOnQuery 因此,我将其更改为BeginExecutenQuery,以及等待线程并调用EndExecutenQuery的代码。这将性能提高约三分之一,但我担心使用同一个SqlConnection会有多个对SqlCommand.BeginExecutenQuery的并发调用 这样行吗,还是会遇到线程问题 对不起,解释得太长了 MSDN文档说明: BeginExecutenQuery方法立即返回,但在代码执行相应的EndExecutenQuery方法调用之前,它不得执行对同一SqlCommand对象启动同步或异步执行的任何其他调用 这似乎意味着不同的SqlCommand对象可以在第一个SqlCommand完成之前调用BeginExecutenQuery 下面是一些说明问题的代码:C# 使用同一SqlConnection对SqlCommand.BeginExecutenQuery的多个并发调用,c#,sql-server,multithreading,ado.net,C#,Sql Server,Multithreading,Ado.net,我有一些工作的C#代码,它使用SqlConnection创建临时表(例如#Foo),调用存储的进程来填充这些临时表并将结果返回给C#客户端,使用C#对这些结果执行复杂的计算,并使用计算结果更新先前创建的一个临时表 由于在整个过程中使用临时表,我们必须只有一个SqlConnection 在用计算结果更新temp表时,我发现了一个性能瓶颈。这段代码已经在批处理更新,以防止C#客户端内存不足。每批计算数据都通过SqlCommand.ExecuteNonQuery发送到存储过程,存储过程反过来更新临时表
private class SqlCommandData
{
public SqlCommand Command { get; set; }
public IAsyncResult AsyncResult { get; set; }
}
public static void TestMultipleConcurrentBeginExecuteNonQueryCalls(string baseConnectionString)
{
var connectionStringBuilder = new SqlConnectionStringBuilder(baseConnectionString)
{
MultipleActiveResultSets = true,
AsynchronousProcessing = true
};
using (var connection = new SqlConnection(connectionStringBuilder.ConnectionString))
{
connection.Open();
// ELIDED - code that uses connection to do various Sql work
SqlDataReader dataReader = null;
// in real code, this would be initialized from calls to SqlCommand.ExecuteReader, using same connection
var commandDatas = new List<SqlCommandData>();
var count = 0;
const int maxCountPerJob = 10000;
while (dataReader.Read())
{
count++;
// ELIDED - do some calculations on data, too complex to do in SQL stored proc
if (count >= maxCountPerJob)
{
count = 0;
var commandData = new SqlCommandData
{
Command = new SqlCommand {Connection = connection}
};
// ELIDED - other initialization of command - used to send the results of calculation back to DB
commandData.AsyncResult = commandData.Command.BeginExecuteNonQuery();
commandDatas.Add(commandData);
}
}
dataReader.Close();
WaitHandle.WaitAll(commandDatas.Select(c => c.AsyncResult.AsyncWaitHandle).ToArray());
foreach (var commandData in commandDatas)
{
commandData.Command.EndExecuteNonQuery(commandData.AsyncResult);
commandData.Command.Dispose();
}
// ELIDED - more code using same SqlConnection to do final work
connection.Close();
}
}
私有类SqlCommandData
{
公共SqlCommand命令{get;set;}
公共IAsyncResult AsyncResult{get;set;}
}
公共静态void TestMultipleConcurrentBeginExecutenQueryCalls(字符串baseConnectionString)
{
var connectionStringBuilder=new-SqlConnectionStringBuilder(baseConnectionString)
{
MultipleActiveResultSets=true,
AsynchronousProcessing=true
};
使用(var connection=newsqlconnection(connectionStringBuilder.ConnectionString))
{
connection.Open();
//ELIDED—使用连接执行各种Sql工作的代码
SqlDataReader=null;
//在实际代码中,这将使用相同的连接从对SqlCommand.ExecuteReader的调用中初始化
var commandDatas=新列表();
var计数=0;
const int maxCountPerJob=10000;
while(dataReader.Read())
{
计数++;
//省略-对数据执行一些计算,这些计算太复杂,无法在SQL存储过程中执行
如果(计数>=maxCountPerJob)
{
计数=0;
var commandData=新的SqlCommandData
{
Command=newsqlcommand{Connection=Connection}
};
//ELIDED-其他命令初始化-用于将计算结果发送回DB
commandData.AsyncResult=commandData.Command.BeginExecutenQuery();
Add(commandData);
}
}
dataReader.Close();
WaitHandle.WaitAll(commandDatas.Select(c=>c.AsyncResult.AsyncWaitHandle.ToArray());
foreach(commandDatas中的var commandData)
{
commandData.Command.EndExecuteNonQuery(commandData.AsyncResult);
commandData.Command.Dispose();
}
//省略-更多代码使用相同的SqlConnection完成最终工作
connection.Close();
}
}
是的,问得很好
也许您可以使用SQL Server 2005中引入的一个功能,称为MARS:
MARS允许重复使用相同的连接进行读写,但它有一些限制,坦白说,我不知道有谁会使用它
从我看来,也许可以从不同的角度来看待你的问题。也许,您可以创建一组包含额外列JobId的永久表,而不是使用临时表并在整个过程中一直关注它们,这最终必须是同步的。这样,您就不受单线程的约束。您可以有一个表来保存作业的历史记录。向该表插入一行后,检索scope_identity()并将其添加到算法的所有元素中。这些表一次可以保存多个结果副本,任何读取或更新数据的查询都将使用JobId作为set标识符。如果您对表进行了正确的索引,您将拥有非常平滑的设计,这将比您现在尝试实现的解决方案具有更大的可伸缩性
问候
Piotr如果运行数据修改语句对您有帮助,那么这就是一个问题。MARS是多个活动结果集的首字母缩略词-结果集是
SELECT
或FETCH
语句的结果,在.NET中,它通常意味着您可以在同一连接上打开多个DataReader。但任何数据修改操作都被视为原子操作,必须先完成,然后才能执行其他操作(或继续从结果集中检索数据)。因此,我认为您的异步命令处于初始状态,并且仍然按顺序执行
如果主连接创建全局临时表##TempName
而不是#Temp
,则可以使用多个连接。地球仪
var commandData = new SqlCommandData
{
Command = new SqlCommand {Connection = connection}
};
connection.Open();
// ELIDED - other initialization of command - used to send the results of calculation back to DB
commandData.AsyncResult = commandData.Command.BeginExecuteNonQuery();