在C#方法中,我执行以下返回多行的SQL查询:
SELECT [Data], [Version]
FROM [dbo].[Table]
WHERE [Id]=@uniqueId AND [ReferenceId] IS NULL
ORDER BY [Version] Asc
然后我迭代结果并调用一个应该更新表的方法:
while (sqlDataReader.Read())
{
SqlBytes data = sqlDataReader.GetSqlBytes(0);
SqlInt64 version = sqlDataReader.GetSqlInt64(1);
UpdateReference(data, version);
}
UpdateReference(data, version)
{
// do database unrelated stuff with data
UPDATE [dbo].[Table]
SET [dbo].[Table].[ReferenceId]=..., [dbo].[Table].[Data]=...
WHERE [dbo].[Table].[Id]=@uniqueId AND [dbo].[Table].[Version]=@version
}
有一段时间这个工作正常,但突然(在同一个表上执行了一些SELECT ... INNER JOIN
查询后)停止了。我在第一个SELECT上创建了一个事务范围(与调用UpdateReference()
的方法相同):
using (TransactionScope scope = new TransactionScope())
SELECT ...
while (sqlDataReader.Read()) ... UpdateReference();
我得到以下异常:
交易已中止。
如果我删除了事务范围,则在调用UPDATE后会发生超时异常:
超时已过期。操作完成之前经过的超时时间或服务器没有响应。
但这似乎不是SQL Server问题。同样奇怪的是,对于某些记录,没有这样的问题 - 它们只发生在某些表记录上使用第一个SELECT时。
这是我到目前为止所发现的:
一个似乎有用的解决方案(现在?)是将第一个查询的结果存储到列表中,然后在SELECT
完成后调用列表元素上的更新:
List<long> versionList = new List<long>();
List<byte[]> dataList = new List<byte[]>();
using (TransactionScope scope = new TransactionScope())
{
using (SqlConnection connection = new SqlConnection(connectionString))
{
connection.Open();
// Execute SELECT ...
using (SqlCommand sqlCommand = new SqlCommand(selectStatement, connection))
{
...
using (SqlDataReader sqlDataReader = sqlCommand.ExecuteReader())
{
while (sqlDataReader.Read())
{
SqlBytes data = sqlDataReader.GetSqlBytes(0);
SqlInt64 version = sqlDataReader.GetSqlInt64(1);
// Store result to lists
versionList.Add(version.Value);
dataList.Add((byte[])data.ToSqlBinary(););
}
}
}
}
// Everything works as expected if this loop is placed here; but if it is placed within the above SqlConnection using clause, an exception is thrown:
// "Network access for Distributed Transaction Manager (MSDTC) has been disabled. Please enable DTC for network access in the security configurationfor MSDTC using the Component Services Administrative tool."
for (int i = 0; i < versionList.Count; i++)
{
UpdateReference(dataList[i], versionList[i]);
}
scope.Complete();
}
我不确定这个解决方案是否有用(除了使用更多的内存而不是最佳)或者它可能导致的其他潜在问题。如果能够深入了解这里发生的事情以及如何最好地解决问题,我将不胜感激。
更新1
为清楚起见,这就是我解决问题的方法:
在TransactionScope外部执行SELECT,将结果存储到列表中;
迭代这些列表并将其内容提供给UPDATE,即 包含在TransactionScope中
随意批评/改进此解决方案:
Method1()
{
List<long> versionList = new List<long>();
List<byte[]> dataList = new List<byte[]>();
using (SqlConnection connection = new SqlConnection(connectionString))
{
connection.Open();
// Execute SELECT ...
using (SqlCommand sqlCommand = new SqlCommand(selectStatement, connection))
{
...
using (SqlDataReader sqlDataReader = sqlCommand.ExecuteReader())
{
while (sqlDataReader.Read())
{
SqlBytes data = sqlDataReader.GetSqlBytes(0);
SqlInt64 version = sqlDataReader.GetSqlInt64(1);
// Store result to lists
versionList.Add(version.Value);
data.Add((byte[])data.ToSqlBinary());
}
}
}
// Call update
for (int i = 0; i < versionList.Count; i++)
{
UpdateReference(dataList[i], versionList[i]);
}
}
}
UpdateReference(data, version)
{
...
using (TransactionScope scope = new TransactionScope())
{
using (SqlConnection connection = new SqlConnection(this.ConnectionString))
{
connection.Open();
UPDATE [dbo].[Table]
SET [dbo].[Table].[ReferenceId]=..., [dbo].[Table].[Data]=...
WHERE [dbo].[Table].[Id]=... AND [dbo].[Table].[Version]=@version
}
scope.Complete();
}
}
答案 0 :(得分:5)
是的,select
通常会锁定;在查询本身期间,为了稳定;但是如果存在事务(取决于隔离级别),那么在查询整个事务之后这些锁可以保持不变;特别是键范围锁。当然,同一事务中的代码不会受到这些锁的不利影响。特别重要的是,完全您的连接是否已打开,以及您使用了多少:
dtcping
可以提供帮助)然而!就个人而言,我怀疑在你的情况下最简单的选择是首先在事务的外进行查询并进入一个列表(或类似的) - 即不是懒惰的假脱机。 然后完成工作,并应用任何更新。如果可能的话,我会尽量避免单个事务跨越数百/数千个单独的命令 - 如果你可以批量工作,那将是更好的。