许多年前,我们有一个最初是作为桌面应用程序编写的应用程序。每当您打开编辑屏幕时,它都会启动一个事务,并在您单击确定时提交,或者在您单击取消时回滚。这对于桌面应用程序来说工作得很好,但现在我们正试图迁移到 ADO.NET 和 SQL Server,长时间运行的事务是有问题的。
我发现当多个用户都试图同时编辑(不同的子集)同一个表时,我们会遇到问题。在我们的旧数据库中,每个用户的事务都会为他们在事务期间修改的每条记录获取记录级锁;由于不同的用户正在编辑不同的记录,每个人都有自己的锁,一切正常。但是在 SQL Server 中,一旦一个用户在事务中编辑了一条记录,SQL Server 似乎就会锁定整个表。当第二个用户尝试编辑同一个表中的不同记录时,第二个用户的应用程序只是锁定,因为 SqlConnection 阻塞,直到第一个用户提交或回滚。
我知道长时间运行的事务是不好的,而且我知道最好的解决方案是更改这些屏幕,以便它们不再长时间保持事务打开。但由于这将意味着一些侵入性和风险的更改,我还想研究是否有办法让这些代码按原样启动和运行,以便我知道我的选择是什么。
如何在 SQL Server 中获取两个不同用户的事务来锁定单个记录而不是整个表?
这是一个快速而肮脏的控制台应用程序,说明了这个问题。我创建了一个名为“test1”的数据库,其中一个名为“Values”的表只有 ID (int) 和 Value (nvarchar) 列。如果您运行该应用程序,它会要求修改 ID、启动事务、修改该记录,然后让事务保持打开状态,直到您按 ENTER。我希望能够
- 启动程序并告诉它更新 ID 1;
- 让它得到它的交易并修改记录;
- 启动程序的第二个副本并告诉它更新 ID 2;
- 让它能够在第一个应用程序的事务仍然打开时更新(和提交)。
目前它在第 4 步冻结,直到我返回应用程序的第一个副本并关闭它或按 ENTER 以提交。对 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)”