SELECT似乎阻止了UPDATE语句

时间:2013-05-30 11:06:17

标签: c# .net sql sql-server-2008

在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时。

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

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

一个似乎有用的解决方案(现在?)是将第一个查询的结果存储到列表中,然后在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

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

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

  2. 迭代这些列表并将其内容提供给UPDATE,即     包含在TransactionScope中

  3. 随意批评/改进此解决方案:

    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();
        }
    }
    

1 个答案:

答案 0 :(得分:5)

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

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

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