【问题标题】:SQL Timeout Expired When It Shouldn'tSQL 超时在不应该的情况下过期
【发布时间】:2015-11-07 00:55:57
【问题描述】:

我正在使用 SqlConnection 类并遇到命令超时到期的问题。

首先,我使用 SqlCommand 属性来设置命令超时,如下所示:

command.CommandTimeout = 300;

此外,我已确保将执行超时设置设置为 0,以确保 SQL 管理方面不存在超时。

这是我的代码:

using (SqlConnection conn = new SqlConnection(connection))
            {
                conn.Open();

                SqlCommand command = conn.CreateCommand();

                var transaction = conn.BeginTransaction("CourseLookupTransaction");

                command.Connection = conn;
                command.Transaction = transaction;
                command.CommandTimeout = 300;

                try
                {
                    command.CommandText = "TRUNCATE TABLE courses";
                    command.ExecuteNonQuery();

                    List<Course> courses = CourseHelper.GetAllCourses();

                    foreach (Course course in courses)
                    {
                        CourseHelper.InsertCourseLookupRecord(course);
                    }

                    transaction.Commit();
                }
                catch (Exception ex)
                {
                    transaction.Rollback();
                    Log.Error(string.Format("Unable to reload course lookup table: {0}", ex.Message));
                }
            }

我已设置日志记录,并且可以在触发此功能后 30 秒准确验证,我在堆栈跟踪中收到以下错误消息:

Timeout expired.  The timeout period elapsed prior to completion of the operation or the server is not responding.

为了全面披露: 在上述 using 语句 foreach 中发现的 InsertCourseLookupRecord() 正在对同一个数据库中的同一个表执行另一个查询。这是它正在执行的查询:

INSERT INTO courses(courseid, contentid, name, code, description, url, metakeywords, metadescription)
VALUES(@courseid, @contentid, @name, @code, @description, @url, @metakeywords, @metadescription)"

此表中有超过 1400 条记录。

我将证明任何帮助我解决此问题的个人都是至高无上的大巫师。

【问题讨论】:

  • CommandTimeout 仅适用于 command 对象。对InsertCourseLookupRecord 的调用不会继承command 的300 秒超时。我怀疑您的 TRUNCATE TABLE 命令导致超时。您能否验证该表是否实际被截断?
  • 我也怀疑它的 TRUNCATE 查询,我应该考虑为在 InsertCourseLookupRecord 中完成的 INSERT 查询设置超时...因为我正在回滚 catch 中的事务,所以我无法验证截断是否确实有效。

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


【解决方案1】:

我相信正在发生的事情是您遇到了导致您在 InsertCourseLookupRecord() 函数中的查询失败的死锁情况。您没有将连接传递给InsertCourseLookupRecord(),所以我假设您在单独的连接中运行它。那么会发生什么:

  • 您已开始交易。
  • 您截断了表格。
  • InsertCourseLookupRecord 启动另一个连接并尝试插入 数据到该表中,但该表被锁定,因为您的 交易尚未提交。
  • 函数 InsertCourseLookupRecord() 中的连接在为该连接定义的 30 秒超时值处超时。

您可以更改函数以接受命令对象作为参数并在函数内部使用它,而不是创建新连接。然后这将成为事务的一部分,并将全部一起提交。

为此,请将您的函数定义更改为:

public static int InsertCourseLookupRecord(string course, SqlCommand cmd)

将所有连接代码从函数中取出,因为您将使用 cmd 对象。然后当您准备好执行查询时:

myCommand.Parameters.Clear();  //need only if you're using command parameters
cmd.CommandText = "INSERT BLAH BLAH BLAH";
cmd.ExecuteNonQuery();

它将在相同的连接和事务上下文下运行。

你在 using 块中这样称呼它:

CourseHelper.InsertCourseLookupRecord(course, command);

您也可以只取 InsertCourseLookupRecord 中的代码并将其放在 for 循环中,然后在您的 using 块中重用命令对象,而根本不需要将其传递给函数。

【讨论】:

  • 在事务开始时有没有办法防止死锁发生?我的理解是,将为第二个查询创建一个单独的事务。当您说将连接传递给函数时,您是指 using 语句中的conn 变量吗?我不太确定如何做到这一点......
  • 您是对的,在 for 循环中对 InsertCourseLookupRecord 的每次调用都必须打开一个新连接,因此它必须在单独的事务中运行。那就是问题所在。一旦您截断表,在您的第一个事务提交之前,不允许其他事务对其执行任何操作。因此,您要么必须在同一个事务中执行所有插入操作,要么提交 truncate table 语句然后执行插入操作。
  • 谢谢布赖恩,这已经非常接近我的问题被接受的答案了。你的认证至尊大巫师的身份就快到了。基于我最初尝试使用此 using 语句修复的问题,我唯一的选择是在同一事务中执行所有插入操作。不幸的是,在这个 foreach 循环中要独立完成大约 1400 次插入。我对如何将事务传递到我的 InserCourseLookupRecord 感到有些困惑。无论如何,您可以在最初的答案中提供一个有用的例子吗?
  • 您是否在其他地方使用了 InsertCourseLookupRecord() 函数?如果不是,我会建议摆脱该功能并将其代码内联。只是不要重新创建任何连接对象并重用命令对象来执行您的插入语句。
  • 嗨大脑,非常感谢你提供了很多清晰的信息!是的,该功能正在其他地方使用,所以不幸的是我无法将该代码转储到其他地方。这也是一种相当复杂的方法。之前负责实现此功能的开发人员使用了 SqlHelper 类,并且正在使用 SqlParameter 构建要插入到查询中的值。在这种情况下,我将使用 Paramaters.Clear()。
【解决方案2】:

因为您使用了两个单独的 SqlConnection 对象,所以由于您在外部代码中开始使用 SqlTransaction,您正在使自己陷入僵局。 InsertCourseLookupReacordGetAllCourses 中的查询被尚未提交的 TRUNCATE TABLE courses 调用阻塞。他们等待 300 秒以提交截断,然后超时。

你有几个选择。

  1. 将 SqlConnection 和 SqlTransaction 传递给 GetAllCoursesInsertCourseLookupRecord,以便它们可以成为同一事务的一部分。
  2. 通过删除SqlTransaction 并改用System.Transaction.TransactionScope 来使用“环境事务”。这会导致所有打开到服务器的连接都共享一个事务。这可能会导致维护问题,具体取决于查询的操作,因为它可能需要调用 Distributed Transaction Coordinator,这可能在某些计算机上被禁用(从您显示的外观来看,您需要 DTC,因为您有两个打开的连接同时)。

最好的选择是尝试更改您的代码以执行选项 1,但如果您不能执行选项 2。

【讨论】:

  • GetAllCourses() 中没有查询。此外,根本没有执行 300 秒的等待。在 using 语句开始后 30 秒,我得到了异常。您是说不可能有两个单独的 SqlConnection 对象吗?一次只能在应用程序中打开一个?
  • 您可以拥有多个,但第二个等待第一个完成才能开始,第一个等待第二个完成才能完成。它们必须是同一事务的一部分,您可以通过它们处于同一连接中或使用TransactionScope 并在机器上运行“分布式事务协调器”服务来做到这一点。 (通常该服务设置为“手动”,并且在大多数台式机上未启动)
【解决方案3】:

取自documentation

CommandTimeout 在针对上下文连接(在连接字符串中使用“context connection=true”打开的 SqlConnection)执行命令时无效

请检查您的连接字符串,这是我能想到的唯一可能性。

【讨论】:

  • 不幸的是,情况并非如此,我的连接字符串中不存在此类属性。
猜你喜欢
  • 2011-05-20
  • 2019-01-31
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2015-04-22
  • 2021-02-04
  • 1970-01-01
  • 2023-01-02
相关资源
最近更新 更多