SQL超时错误,明明不应该超时

4

我是使用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语句中的InsertCourseLookupRecord()在循环中执行了另一个查询,查询的是同一数据库中的同一张表。以下是它执行的查询语句:

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

这个表格中有超过1400条记录。

我将认证任何帮助我解决此问题的个人为最高大法师。


1
“CommandTimeout” 只适用于 “command” 对象。对 “InsertCourseLookupRecord” 的调用不会继承来自 “command”的 300 秒超时。我怀疑你的 “TRUNCATE TABLE” 命令是否导致了超时。你能验证一下表是否真的被截断了吗? - Thomas Stringer
我怀疑问题不在于“TRUNCATE”查询,我应该研究一下在InsertCourseLookupRecord中设置“INSERT”查询的超时时间。因为我在“catch”中回滚了事务,所以无法验证截断是否有效。 - jayrue
3个回答

4
我认为您遇到了死锁的情况,导致您在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();

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

在使用块中,您可以这样调用它:

CourseHelper.InsertCourseLookupRecord(course, command);

您也可以将InsertCourseLookupRecord中的代码放在for循环内部,然后在使用块中重复使用命令对象,而无需将其传递给函数。


有没有办法在事务开始时防止死锁发生?我的理解是第二个查询将创建一个单独的事务。当你说将连接传递给函数时,你是指using语句中的“conn”变量吗?我不太确定应该如何做... - jayrue
你是正确的,在你的for循环中每次调用InsertCourseLookupRecord都必须打开一个新连接,因此它必须在单独的事务中运行。这就是问题所在。一旦你截断了表,直到你的第一个事务提交之前,不允许其他任何事务对其进行任何操作。因此,你要么在同一个事务中执行所有插入操作,要么提交截断表语句,然后再执行插入操作。 - Brian Pressler
谢谢Brian,这已经非常接近成为我问题的被接受答案了。你的认证至高无上的巫师地位已经近在眼前。基于我最初尝试使用此using语句修复的问题,我的唯一选择是在同一个事务中执行所有插入操作。不幸的是,在此foreach循环中需要独立完成大约1400个插入操作。我有点困惑如何将事务传递给我的InserCourseLookupRecord方法。您能否在您的初始答案中提供一个有用的示例呢? - jayrue
你在其他地方使用InsertCourseLookupRecord()函数吗?如果没有,我建议你将该函数取消并将其代码内联化。只需不重新创建任何连接对象并重用命令对象来执行您的插入语句即可。 - Brian Pressler
嗨Brain,非常感谢您提供了如此清晰的解释!是的,该函数在其他地方也被使用,所以不幸的是我不能将那段代码转移到其他地方。这是一个相当复杂的方法。之前负责编写此方法的开发人员使用了SqlHelper类,并使用SqlParameter来构建要插入查询的值。在这种情况下,我将使用Paramaters.Clear()。 - jayrue
我还没有尝试过这个,但它看起来不错并且很有道理。我对这个答案感到满意,所以我会将其标记为解决方案。谢谢你帮我弄清楚这个问题 :) - jayrue

1
因为你正在使用两个不同的 SqlConnection 对象,所以由于外部代码中启动的 SqlTransaction,你会发生死锁。在 InsertCourseLookupReacord 和也许是 GetAllCourses 中的查询被 TRUNCATE TABLE courses 调用阻塞,该调用尚未提交。它们等待300秒来提交截断,然后超时。
你有几个选项。
  1. 将SqlConnection和SqlTransaction传入GetAllCoursesInsertCourseLookupRecord,以便它们可以成为同一事务的一部分。
  2. 通过去掉SqlTransaction并使用System.Transaction.TransactionScope来使用“环境事务”。这会导致所有打开到服务器的连接都共享一个事务。这可能会引起维护问题,因为根据查询的操作可能需要调用Distributed Transaction Coordinator,在某些计算机上可能已禁用(从你所展示的内容看,你需要DTC,因为你同时有两个打开的连接)。

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


GetAllCourses()中没有完成任何查询。此外,根本没有等待300秒钟。我在using语句开始后30秒钟精确地收到了异常。你是说一个应用程序中不可能有两个单独的SqlConnection对象吗?一次只能打开一个? - jayrue
您可以有多个事务,但第二个事务需要等待第一个事务完成后才能开始,而第一个事务则需要等待第二个事务完成后才能结束。它们必须是同一事务的一部分,您可以通过它们在同一连接中或使用“TransactionScope”来实现,并且在计算机上运行“分布式事务协调器”服务来实现。 (通常该服务设置为“手动”,并且在大多数桌面计算机上未启动) - Scott Chamberlain

0

摘自文档

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

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


很不幸,我的连接字符串中没有这样的属性。 - jayrue

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接