23

ADO.net のラッパーとして機能する「データベース」クラスがあります。たとえば、プロシージャを実行する必要がある場合は、Database.ExecuteProcedure(procedureName, parametersAndItsValues) を呼び出します。

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 ブロック内で行うことはできますか?

4

4 に答える 4

40

まず、SQL 2000コードを確認し、このデッドロックが発生している理由を突き止めます。これを修正すると、より大きな問題が隠れている可能性があります(たとえば、インデックスの欠落やクエリの不良)。

次に、アーキテクチャを確認して、デッドロックステートメントを実際に頻繁に呼び出す必要があることを確認します(select count(*) from bob1秒間に100回呼び出す必要がありますか?)。

ただし、デッドロックのサポートが本当に必要で、SQLまたはアーキテクチャにエラーがない場合は、次の手順に従って試してください。(注:1秒あたり数千のクエリをサポートするシステムでこの手法を使用する必要があり、デッドロックが発生することはめったにありません)

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;
  }
}
于 2008-12-02T22:18:49.663 に答える
24

@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;
        }
    });
}
于 2011-07-14T11:13:50.490 に答える
5

デッドロックがデータ層で解決できるのであれば、それが間違いなく進むべき道です。ヒントのロック、モジュールの動作方法の再設計など。ただし、NoLock は万能薬ではありません。トランザクションの整合性のために使用できない場合があり、関連するすべてのテーブルを NoLock したまま (複雑ではありますが) データを読み取り、他のクエリでブロックが発生するケースがありました。

とにかく、何らかの理由でデータ層で解決できない場合はどうですか

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;
    }
}
于 2008-12-02T10:22:03.807 に答える
4

デッドロックの問題が発生している場合は、SQL コードが何を行っているかを確認することをお勧めします。たとえば、シリアル化可能な分離レベル (または rdbms に相当するもの) がある場合、ロック エスカレーション デッドロックは非常に簡単に作成でき、クエリの並べ替えなどのいくつかの方法で軽減できます。少なくとも) (UPDLOCK) を使用して、以前に書き込みロックを取得します (競合する読み取りロックを取得しないようにします)。

再試行は混合されます...たとえば、TransactionScope にいる場合は、既に中止されている可能性があります。しかし、純粋なレベルでは、データベースとの通信に問題が発生した場合は、コードをパニックに陥らせ、早期にパニックに陥らせたいと考えています...この特定のシナリオでは、再試行は少しハッキリしているようです。

于 2008-11-26T13:15:20.790 に答える