【问题标题】:Timeout error when result of a transaction is used as parameter将事务结果用作参数时的超时错误
【发布时间】:2012-12-11 15:28:34
【问题描述】:

我有以下使用 SqlTransaction 的代码

string connectionString = ConfigurationManager.AppSettings["ConnectionString"];
using (SqlConnection connection = new SqlConnection(connectionString))
{
    connection.Open();
    SqlTransaction transaction = connection.BeginTransaction();

    int logID = HelperClass.InsertException(connection, 1, DateTime.Now, "Y", "test", "test", 1, 1, transaction);
    LogSearch logSearch = new LogSearch();
    logSearch.LogID = 258;

    Collection<Log> logs = HelperClass.GetLogs(logSearch, connectionString);
}

此代码引发以下异常

超时。在操作完成之前超时时间已过或服务器没有响应。

但是,如果我为 LogID 传递硬编码值,也不例外。

问题

  1. 为什么当我传递 logID 时会出现异常(InsertException() 的结果)?
  2. 请解释为什么当我将 hard coded 值作为 LogID 传递时没有异常

注意:InsertException() 使用与SqlTransaction 的连接,而 GetLogs() 使用没有任何事务的新连接

更新问题

业务层代码不使用Transaction。我需要在上面显示的单元测试代码中调用业务层方法(用于集成测试)。即使业务层不使用事务,我们如何将事务应用于 UT 代码(用于集成测试)?从@jbl 的回答看来,在单元测试中根本不可能使用事务。 UT代码如何申请交易。

代码

public static class HelperClass
{
    public static Collection<Log> GetLogs(LogSearch logSearch, string connectionString)
    {
        Collection<Log> logs = new Collection<Log>();
        using (SqlConnection connection = new SqlConnection(connectionString))
        {
            connection.Open();
            string commandText = "SELECT * FROM Application_EX WHERE application_ex_id = @application_ex_id";
            using (SqlCommand command = new SqlCommand(commandText, connection))
            {
                command.CommandType = System.Data.CommandType.Text;

                //Parameter value setting
                command.Parameters.AddWithValue("@application_ex_id", logSearch.LogID);

                using (SqlDataReader reader = command.ExecuteReader())
                {
                    if (reader.HasRows)
                    {
                        while (reader.Read())
                        {

                        }
                    }

                }
            }
        }

        return logs;

    }

    public static Int16 InsertException(SqlConnection connection, Int16 applicationID, DateTime createdDate, string isInternalLocationIndicator, string exceptionDescription, string operation, Int16 severityLevelNumber, Int16 exceptionTypeCode, SqlTransaction transaction)
    {


        Int16 newLogID = 0;
        string commandText = @"INSERT INTO Application_Ex
                            VALUES (@severity_level_nbr, @appl_service_id, @ex_internal_appl_ind, 
                            @application_ex_txt,@ex_location_txt,@create_ts,@application_ex_code);
                            SELECT SCOPE_IDENTITY() AS [LogIdentity];";
        using (SqlCommand command = new SqlCommand(commandText, connection, transaction))
        {
            command.CommandType = System.Data.CommandType.Text;
            command.Parameters.AddWithValue("@severity_level_nbr", severityLevelNumber);
            command.Parameters.AddWithValue("@appl_service_id", applicationID);
            command.Parameters.AddWithValue("@ex_internal_appl_ind", isInternalLocationIndicator);
            command.Parameters.AddWithValue("@application_ex_txt", exceptionDescription);
            command.Parameters.AddWithValue("@ex_location_txt", operation);
            command.Parameters.AddWithValue("@create_ts", createdDate);
            command.Parameters.AddWithValue("@application_ex_code", exceptionTypeCode);

            newLogID = Convert.ToInt16(command.ExecuteScalar(), CultureInfo.InvariantCulture);
        }
        return newLogID;

    }
}

【问题讨论】:

  • 您声明“业务层代码 (HelperClass.InsertException) 不使用 Transaction”,但 command = new SqlCommand(..., transaction) 显然确实使用了事务。这是您的问题,因为LogSearch 正在尝试选择HelperClass 插入的未提交(因此锁定)数据。

标签: c# concurrency ado.net transactions


【解决方案1】:

问题

  1. 为什么我传递 logID 时会出现异常(InsertException() 的结果)?
  2. 请解释为什么当我将硬编码值作为 LogID 传递时没有异常

答案

  1. 当使用事务插入新记录时,这意味着在您自己提交事务之前不会最终提交新记录。在此之前,新记录被锁定,这意味着任何触及该新记录的查询都将被挂起,直到发生超时。在您的情况下,对 GetLogs 方法的调用在事务仍在运行时执行,并且该方法搜索新插入的记录 ID。由于该行被锁定,您的 GetLogs 调用将一直等到发生超时,这会导致超时异常。
  2. 在硬编码值的情况下,调用 GetLogs 将搜索具有相应 id 的现有记录。由于您正在搜索 PK 值,因此 SQL 不必搜索所有行,因为它是 PK。因此,现有行会在单独的事务中找到并返回,因为事务在它们接触的数据中没有重叠。

假设您的方法 GetLogs 正在搜索另一列上的表,即非 pk 列(例如 application_message),那么必须读取整个表才能找到具有 application_message 对应值的行(或行) .这将导致查询始终触及新插入的锁定行,然后使用硬编码 (application_message) 值,您将获得超时异常。我添加这个只是为了澄清锁定,以及 SQL 何时需要或不需要触摸锁定的行。

希望这会有所帮助。

【讨论】:

  • 您是说我们在使用Unit Testing 代码进行集成测试时无法应用事务?
  • 您可以尝试使用 TransactionScope msdn.microsoft.com/en-us/library/… 包围您的测试代码。这将以环境方式在同一事务中运行您的连接。不确定它是否会起作用,因为当您使用第二个查询调用该方法时,第一个查询的连接仍然在您的代码中打开。不过值得一试。
【解决方案2】:

我猜这是因为HelperClass.GetLogs(logSearch, connectionString); 实例化了一个超出您事务范围的新连接:

您可以随意:

  • 让你的帮助类接受持有事务的连接对象而不是连接字符串
  • 或将"SELECT * FROM Application_EX WHERE application_ex_id = @application_ex_id" 替换为"SELECT * FROM Application_EX with (nolock) WHERE application_ex_id = @application_ex_id"

请注意,第二种情况有时会返回不正确的值,并且不会返回您当前在事务中插入的值

希望这会有所帮助

【讨论】:

  • 我不明白为什么它应该返回异常。数据未在第一次调用中提交。没事儿。但是为什么会有例外呢?
  • 因为在事务完成之前对应的行都被锁定了,防止了表被查询。你的助手等待一个永远不会到来的锁释放(因为释放应该在助手成功运行后发生)
  • 请解释为什么当我将硬编码值作为 LogID 传递时没有异常
  • @Lijo :这是因为系统能够计算出硬编码值行与当前插入的值之间没有冲突。所以它可以安全地返回硬编码的值行。无论您提交还是回滚事务,这些都是相同的。这是一个行级锁。
  • @Lijo 你可以从阅读事务隔离级别开始:msdn.microsoft.com/en-us/library/ms173763.aspx
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2018-04-11
  • 1970-01-01
  • 2012-12-01
相关资源
最近更新 更多