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;
}
キャッチブロック内でこの処理を行うことはできますか?
まず、SQL 2000コードを確認し、このデッドロックが発生している理由を突き止めます。これを修正すると、より大きな問題(インデックスの欠落やクエリの不良など)が隠れている可能性があります。
次に、アーキテクチャを確認して、デッドロックステートメントを実際に頻繁に呼び出す必要があることを確認します(select count(*) from bob
を1秒間に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;
}
}
@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;
}
});
}
データレイヤーでデッドロックを解決できるのであれば、それが間違いなく進むべき道です。ヒントのロック、モジュールの動作方法の再設計など。ただし、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;
}
}
デッドロックで問題が発生した場合は、SQLコードの動作を確認することをお勧めします。たとえば、シリアル化可能な分離レベル(またはそれに相当するものがrdbmsにあるもの)がある場合、ロックエスカレーションデッドロックは非常に簡単に作成できます。また、クエリの並べ替えなど、いくつかの方法で軽減できます。または(SQL Serverの場合)少なくとも)(UPDLOCK)を使用して、書き込みロックを早期に取得します(したがって、競合する読み取りロックを取得しません)。
再試行は混合されます...たとえば、TransactionScopeを使用している場合は、すでに中止されている可能性があります。しかし、純粋主義者レベルでは、データベースとの通信で問題が発生した場合は、コードをパニックにし、早期にパニックを起こします...この特定のシナリオでは、再試行は少しハックのようです。