简体   繁体   中英

SQL Timeout Expired When It Shouldn't

I am using the SqlConnection class and running into problems with command time outs expiring.

First off, I am using the SqlCommand property to set a command timeout like so:

command.CommandTimeout = 300;

Also, I have ensured that the Execution Timeout setting is set to 0 to ensure that there should be no timeouts on the SQL Management side of things.

Here is my code:

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

I have set up logging and can verify exactly 30 seconds after firing off this function, I receive the following error message in my stack trace:

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

In the interest of full disclosure: InsertCourseLookupRecord() found inside the above using statements foreach, is performing another query to the same table in the same database. Here is the query it is performing:

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

There is over 1400 records in this table.

I will certify any individual(s) that helps me solve this as a most supreme grand wizard.

I believe what is happening is that you have a deadlock situation that is causing your query in the InsertCourseLookupRecord() function to fail. You are not passing your connection to InsertCourseLookupRecord() so I am assuming you are running that in a separate connection. So what happens is:

  • You started a transaction.
  • You truncate the table.
  • InsertCourseLookupRecord starts another connection and tries to insert data into that table, but the table is locked because your transaction isn't committed yet.
  • The connection in the function InsertCourseLookupRecord() times out at the timeout value defined for that connection of 30 seconds.

You could change the function to accept the command object as a parameter and use it inside the function instead of creating a new connection. This will then become part of the transaction and will all be committed together.

To do this change your function definition to:

public static int InsertCourseLookupRecord(string course, SqlCommand cmd)

Take all the connection code out of the function because you're going to use the cmd object. Then when you're ready to execute your query:

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

It will run under the same connection and transaction context.

You call it like this in your using block:

CourseHelper.InsertCourseLookupRecord(course, command);

You could also just take the code in the InsertCourseLookupRecord and put it inside the for loop instead and then reuse the command object in your using block without needing to pass it to a function at all.

Because you are using two separate SqlConnection objects you are deadlocking your self due to the SqlTransaction you started in your outer code. The query in InsertCourseLookupReacord and maybe in GetAllCourses get blocked by the TRUNCATE TABLE courses call which has not yet been committed. They wait 300 seconds for the truncate to be committed then time out.

You have a few options.

  1. Pass the SqlConnection and SqlTransaction in to GetAllCourses and InsertCourseLookupRecord so they can be part of the same transaction.
  2. Use a "ambient transaction" by getting rid of the SqlTransaction and using a System.Transaction.TransactionScope instead. This causes all connections that are opened to the server all share a transaction. This can cause maintenance issues as depending on what the queries are doing it as it may need to invoke the Distributed Transaction Coordinator which may be disabled on some computers (from the looks of what you showed you would need the DTC as you have two open connections at the same time).

The best option is try to change your code to do option 1, but if you can't do option 2.

Taken from the documentation :

CommandTimeout has no effect when the command is executed against a context connection (a SqlConnection opened with "context connection=true" in the connection string)

Please review your connection string, thats the only possibility I can think of.

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM