rob*_*rke 28 c# sql-server ado.net multithreading
我有一些使用SqlConnection创建临时表(例如#Foo)的C#代码,调用存储过程来填充那些临时表并将结果返回给C#客户端,使用c#对这些结果执行复杂计算,并使用计算结果更新之前创建的临时表之一.
由于整个过程中使用的临时表,我们必须只有一个SqlConnection.
我发现了使用计算结果更新临时表时的性能瓶颈.此代码已经对更新进行批处理,以防止C#客户端内存不足.每批计算数据通过SqlCommand.ExecuteNonQuery发送到存储过程,然后sproc会更新临时表.代码将大部分时间花在对ExecuteNonQuery的调用上.
因此,我将其更改为BeginExecuteNonQuery,以及在线程上等待并调用EndExecuteNonQuery的代码.这提高了性能约三分之一,但我担心使用相同的SqlConnection对SqlCommand.BeginExecuteNonQuery进行多次并发调用.
这样可以,还是会遇到线程问题?
很抱歉很长的解释.
MSDN文档声明:
BeginExecuteNonQuery方法立即返回,但在代码执行相应的EndExecuteNonQuery方法调用之前,它不能执行任何其他对同一SqlCommand对象启动同步或异步执行的调用.
这似乎意味着不同的SqlCommand对象可以在第一个SqlCommand完成之前调用BeginExecuteNonQuery.
以下是一些说明问题的代码:
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();
}
}
Run Code Online (Sandbox Code Playgroud)
csh*_*net 18
那么,在收到很多票数的极大风险我不得不评论这个.首先,这是一个很好的问题,可以很好地解决您提到的具体潜在问题.但是,你忽略了讨论你想要完成的这个"漫长"的过程.
我的经历让我觉得有一点......
如果您提出的问题很难回答,请更改问题.
虽然我对你的具体问题知之甚少,但我认为这很适合你的困境.正如其他人所提到的......临时表很讨厌,为特定任务创建自己的表仍然更糟糕,在SQL中更新大量数据是昂贵的.
问问自己"你能避免这一切吗?"
人们常常选择在数据库中实现极其复杂的逻辑,因为他们相信SQL可以更快地完成它.实际上这是一个有缺陷的概念,数据库是存储/序列化设备,它们擅长存储,更新,定位和同步数据访问.它们不适合处理复杂的操作.即使在微软(以及其他人)通过向其中注入完整的开发语言来对数据库进行混淆之后,它也无法像编写良好的客户端那样表现最佳(*取决于操作的复杂性,我怀疑你已超越).
例如,您有一个大约2GB的原始数据的数据库.您希望在整个数据集上生成复杂的报告或分析.好吧,简单地说2GB的内存很容易通过使用词典或其他什么来创建你需要的查找,将整个数据库(或你需要的部分)啜饮到内存中.根据几个因素,整个事情可能比SQL快几倍,可以轻松地进行单元测试,并且(恕我直言)比构建动态SQL的各种各样的SPROC更容易构建,调试和维护.即使有超过2GB的原始数据,也可以使用几种现有技术(B-Trees,ISAM等)轻松创建客户端缓存.
我今天工作的产品在数据库中有2.4tb的数据,我们没有一个sproc,join语句,甚至没有相等的where子句.
但是,我的建议可能与您的具体情况有关,也可能不相关,因为我不了解您的目标或限制因素.希望如果没有别的,它会让你问自己:
"我问的是正确的问题吗?"
小智 1
是的,确实是个好问题。
也许您可以使用 SQL Server 2005 中引入的一项称为 MARS 的功能: http: //msdn.microsoft.com/en-us/library/ms345109 (v=sql.90).aspx
MARS 允许重复使用相同的连接进行读取和写入,但它有一些限制,坦率地说,我不知道有人会使用它。
但从我看来,也许可以从不同的角度来看待你的问题。也许,您可以创建一组包含附加列 JobId 的永久表,而不是使用临时表并在整个过程中密切关注它们,这最终必须是同步的。那么你就不再局限于单线程。您可以有一个表来保存作业的历史记录。在此表中插入一行后,您将检索scope_identity() 并将其添加到算法的所有元素中。这些表一次可以保存多个结果副本,并且任何读取或更新数据的查询都将使用 JobId 作为设置标识符。如果正确索引表,您将拥有非常流畅的设计,比您现在尝试实现的解决方案更具可扩展性。
问候
皮奥特尔
| 归档时间: |
|
| 查看次数: |
14932 次 |
| 最近记录: |