何年も前に、もともとデスクトップアプリとして作成されたアプリケーションがあります。編集画面を開くたびにトランザクションを開始し、[OK]をクリックするとコミットし、[キャンセル]をクリックするとロールバックします。これはデスクトップアプリでは問題なく機能しましたが、現在はADO.NETとSQL Serverに移行しようとしており、長時間実行されるトランザクションには問題があります。
複数のユーザーがすべて同じテーブル(の異なるサブセット)を同時に編集しようとすると、問題が発生することがわかりました。古いデータベースでは、各ユーザーのトランザクションは、トランザクション中に変更したすべてのレコードに対するレコードレベルのロックを取得していました。さまざまなユーザーがさまざまなレコードを編集していたため、全員が独自のロックを取得し、すべてが機能します。ただし、SQL Serverでは、1人のユーザーがトランザクション内のレコードを編集するとすぐに、SQLServerはテーブル全体をロックしているように見えます。2番目のユーザーが同じテーブル内の別のレコードを編集しようとすると、最初のユーザーがコミットまたはロールバックするまでSqlConnectionがブロックされるため、2番目のユーザーのアプリは単にロックアップします。
長時間実行されるトランザクションが悪いことは承知しています。最善の解決策は、これらの画面を変更して、トランザクションを長時間開いたままにしないようにすることです。しかし、それは侵襲的でリスクの高い変更を意味するので、このコードをそのまま稼働させる方法があるかどうかも調査したいと思います。そうすれば、自分の選択肢が何であるかがわかります。
SQL Serverで2人の異なるユーザーのトランザクションを取得して、テーブル全体ではなく個々のレコードをロックするにはどうすればよいですか?
これは、問題を説明する手っ取り早いコンソールアプリです。「test1」というデータベースを作成しました。「Values」というテーブルが1つあり、ID(int)列とValue(nvarchar)列だけがあります。アプリを実行すると、変更するIDを要求され、トランザクションが開始され、そのレコードが変更されてから、Enterキーを押すまでトランザクションが開いたままになります。できるようになりたい
- プログラムを起動し、ID1を更新するように指示します。
- トランザクションを取得してレコードを変更します。
- プログラムの2番目のコピーを開始し、ID2を更新するように指示します。
- 最初のアプリのトランザクションがまだ開いている間に、更新(およびコミット)できるようにします。
現在、アプリの最初のコピーに戻ってアプリを閉じるか、Enterキーを押してコミットするまで、手順4でフリーズします。command.ExecuteNonQueryの呼び出しは、最初の接続が閉じられるまでブロックされます。
public static void Main()
{
Console.Write("ID to update: ");
var id = int.Parse(Console.ReadLine());
Console.WriteLine("Starting transaction");
using (var scope = new TransactionScope())
using (var connection = new SqlConnection(@"Data Source=localhost\sqlexpress;Initial Catalog=test1;Integrated Security=True"))
{
connection.Open();
var command = connection.CreateCommand();
command.CommandText = "UPDATE [Values] SET Value = 'Value' WHERE ID = " + id;
Console.WriteLine("Updating record");
command.ExecuteNonQuery();
Console.Write("Press ENTER to end transaction: ");
Console.ReadLine();
scope.Complete();
}
}
動作に変更を加えずに、私がすでに試したいくつかのことを次に示します。
- トランザクション分離レベルを「コミットされていない読み取り」に変更する
- UPDATEステートメントで「WITH(ROWLOCK)」を指定する