Как получить эффективную обработку блокировки сервера Sql в С# с помощью ADO?
У меня есть класс "База данных", который работает как оболочка для ADO.net. Например, когда мне нужно выполнить процедуру, я вызываю Database.ExecuteProcedure(procedureName, parametersAndItsValues).
Мы столкнулись с серьезными проблемами с ситуациями Deadlock в SQL Server 2000. Часть нашей команды работает над кодом и транзакциями sql для минимизации этих событий, но я думаю о том, чтобы сделать этот класс базы данных надежным в ситуациях взаимоблокировки.
Мы хотим, чтобы жертва тупика пыталась повторить попытку, возможно, через некоторое время, но я не знаю, возможно ли это. Вот код для метода, который мы используем:
public int ExecuteQuery(string query)
{
int rows = 0;
try
{
Command.Connection = Connection;
Command.CommandType = CommandType.Text;
if(DatabaseType != enumDatabaseType.ORACLE)
Command.CommandText = query;
else
Command.CommandText ="BEGIN " + query + " END;";
if (DatabaseType != enumDatabaseType.SQLCOMPACT)
Command.CommandTimeout = Connection.ConnectionTimeout;
if (Connection.State == ConnectionState.Closed)
Connection.Open();
rows = Command.ExecuteNonQuery();
}
catch (Exception exp)
{
//Could I add here any code to handle it?
throw new Exception(exp.Message);
}
finally
{
if (Command.Transaction == null)
{
Connection.Close();
_connection.Dispose();
_connection = null;
Command.Dispose();
Command = null;
}
}
return rows;
}
Могу ли я сделать эту обработку внутри блока catch?
Ответы
Ответ 1
Во-первых, я бы пересмотрел свой код SQL 2000 и понял, почему этот тупик происходит. Исправление этого может скрыть большую проблему (например, отсутствующий индекс или плохой запрос).
Во-вторых, я бы пересмотрел свою архитектуру, чтобы подтвердить, что оператор deadlocking действительно нужно часто называть (следует ли select count(*) from bob
вызывать 100 раз в секунду?).
Однако, если вам действительно нужна поддержка тупика и нет ошибок в вашем SQL или архитектуре, попробуйте что-нибудь в следующих строках. (Примечание: мне пришлось использовать этот метод для системы, поддерживающей тысячи запросов в секунду, и очень редко ударял бы в тупик)
int retryCount = 3;
bool success = false;
while (retryCount > 0 && !success)
{
try
{
// your sql here
success = true;
}
catch (SqlException exception)
{
if (exception.Number != 1205)
{
// a sql exception that is not a deadlock
throw;
}
// Add delay here if you wish.
retryCount--;
if (retryCount == 0) throw;
}
}
Ответ 2
Основываясь на ответе @Sam, я представляю метод обертки общего назначения:
private static T Retry<T>(Func<T> func)
{
int count = 3;
TimeSpan delay = TimeSpan.FromSeconds(5);
while (true)
{
try
{
return func();
}
catch(SqlException e)
{
--count;
if (count <= 0) throw;
if (e.Number == 1205)
_log.Debug("Deadlock, retrying", e);
else if (e.Number == -2)
_log.Debug("Timeout, retrying", e);
else
throw;
Thread.Sleep(delay);
}
}
}
private static void Retry(Action action)
{
Retry(() => { action(); return true; });
}
// Example usage
protected static void Execute(string connectionString, string commandString)
{
_log.DebugFormat("SQL Execute \"{0}\" on {1}", commandString, connectionString);
Retry(() => {
using (SqlConnection connection = new SqlConnection(connectionString))
using (SqlCommand command = new SqlCommand(commandString, connection))
command.ExecuteNonQuery();
});
}
protected static T GetValue<T>(string connectionString, string commandString)
{
_log.DebugFormat("SQL Scalar Query \"{0}\" on {1}", commandString, connectionString);
return Retry(() => {
using (SqlConnection connection = new SqlConnection(connectionString))
using (SqlCommand command = new SqlCommand(commandString, connection))
{
object value = command.ExecuteScalar();
if (value is DBNull) return default(T);
return (T) value;
}
});
}
Ответ 3
Если тупик может быть разрешен на уровне данных, это определенно способ пойти. Блокировка подсказок, пересмотр способа работы модуля и т.д. NoLock - это не панацея, хотя иногда это невозможно использовать по соображениям целостности транзакций, и у меня были случаи прямого (хотя и сложного) чтения данных со всеми соответствующими таблицами NoLock'd, которые по-прежнему вызывали блоки по другим запросам.
В любом случае - если вы не можете решить его на уровне данных по любой причине, как насчет
bool OK = false;
Random Rnd = new Random();
while(!OK)
{
try
{
rows = Command.ExecuteNonQuery();
OK = true;
}
catch(Exception exDead)
{
if(exDead.Message.ToLower().Contains("deadlock"))
System.Threading.Thread.Sleep(Rnd.Next(1000, 5000));
else
throw exDead;
}
}
Ответ 4
Если у вас возникают проблемы с взаимоблокировками, было бы лучше посмотреть, что делает код SQL. Например, блокировка блокировки блокировки очень проста в создании, если у вас есть сериализуемый уровень изоляции (или что-то подобное в ваших rdbms) - и может быть смягчено несколькими способами, такими как повторный заказ запросов или (в SQL Server по крайней мере) с помощью (UPDLOCK) для блокировки записи раньше (так что вы не получаете конкурирующего чтения-блокировки).
Повторная попытка будет смешанной... например, если вы находитесь в TransactionScope, она, возможно, уже прервана. Но как раз на уровне пуриста - если у меня возникают проблемы с разговором с db, я хочу, чтобы мой код паниковал, и панику рано... Повторите попытку, кажется, немного взломанный в этом конкретном сценарии.