SELECT似乎阻止了UPDATE语句

w12*_*128 3 .net c# sql sql-server-2008

在C#方法中,我执行以下返回多行的SQL查询:

SELECT [Data], [Version] 
FROM [dbo].[Table] 
WHERE [Id]=@uniqueId AND [ReferenceId] IS NULL 
ORDER BY [Version] Asc
Run Code Online (Sandbox Code Playgroud)

然后我迭代结果并调用一个应该更新表的方法:

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
}
Run Code Online (Sandbox Code Playgroud)

有一段时间这个工作正常,但突然(在SELECT ... INNER JOIN同一个表上执行一些查询后)停止了.我在第一个SELECT上创建了一个事务范围(在调用的方法中UpdateReference()):

 using (TransactionScope scope = new TransactionScope())
    SELECT ...
    while (sqlDataReader.Read()) ... UpdateReference();
Run Code Online (Sandbox Code Playgroud)

我得到以下异常:

交易已中止.

如果我删除了事务范围,则在调用UPDATE一段时间后会发生超时异常:

超时已过期.操作完成之前经过的超时时间或服务器没有响应.

但这似乎不是SQL Server的问题.同样奇怪的是,对于某些记录,没有这样的问题 - 它们只发生在某些表记录上使用第一个SELECT时.

这是我到目前为止发现的:

  • 如果我独立地执行查询(来自代码),一切正常;
  • 如果我在SQL Management Studio中独立执行它们,则两个查询都按预期工作

一个似乎有用的解决方案(现在?)是将第一个查询的结果存储到列表中,然后在SELECT完成后调用list元素上的update :

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();
}
Run Code Online (Sandbox Code Playgroud)

我不确定这个解决方案是否有用(除了使用更多的内存而不是最佳)或者它可能导致的其他潜在问题.如果能够深入了解这里发生的事情以及如何最好地解决问题,我将不胜感激.

更新1

为清楚起见,这是我解决问题的方法:

  1. 在TransactionScope外部执行SELECT,将结果存储到列表中;

  2. 迭代这些列表并将其内容提供给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();
    }
}
Run Code Online (Sandbox Code Playgroud)

Mar*_*ell 5

是的,select通常需要锁; 在查询本身期间,为了稳定; 但是如果存在事务(取决于隔离级别),那么在查询整个事务之后这些锁可以保持不变; 特别是键范围锁.当然,同一事务中的代码不会受到这些锁的不利影响.特别重要的是打开创建的连接的确切位置,以及您使用的连接数:

  • 如果在该事务中创建并打开了连接,则该连接将仅在环境事务中自动登记; 如果您打开连接然后创建环境事务,则连接不会自动登记
  • 如果在事务范围内有单个连接,它通常会使用LTM; 如果您使用多个连接实例,它通常只会升级到DTC; DTC在网络上配置有点繁琐(dtcping可以帮助)
  • 你的情况,你想有一个阅读器和执行的同时 ; 我怀疑你现在正在使用多个连接这样做; 另一个选项是启用MARS,它允许您在单个连接上执行两个操作

然而!就个人而言,我怀疑在你的情况下最简单的选择是首先在事务之外进行查询并进入列表(或类似) - 即不是懒惰的假脱机.然后完成工作,并应用任何更新.如果可能的话,我会尽量避免单个事务跨越数百/数千个单独的命令 - 如果你可以批量工作,那将是更好的选择.