3

我们有一个最初作为桌面应用程序编写的应用程序,这些应用程序在很多年前都是这样。无论何时打开编辑屏幕,它都会启动事务,并在您单击确定时提交;如果单击取消,则会回滚。这对于桌面应用程序来说工作得很好,但现在我们正在尝试转向ADO.NET和SQL Server,并且长时间运行的事务是有问题的。我怎样才能让SQL Server事务使用记录级锁?

我发现当多个用户都试图同时编辑同一个表(不同的子集)时,我们会遇到问题。在我们的旧数据库中,每个用户的事务将获取记录级锁,以锁定他们在事务中修改的每条记录;因为不同的用户正在编辑不同的记录,每个人都有自己的锁,一切正常。但是在SQL Server中,只要有一个用户在一个事务中编辑了一条记录,SQL Server似乎就会在整个表上获得一个锁。当第二个用户试图编辑同一个表中的另一个记录时,第二个用户的应用程序会简单地锁定,因为SqlConnection会阻塞,直到第一个用户提交或回滚。

我知道长时间运行的交易很糟糕,而且我知道最好的解决方案是更改这些屏幕,以便它们不再让交易长时间保持打开状态。但是,由于这意味着一些侵入性和风险性的变化,我还想研究是否有办法让代码正常运行,只是让我知道我的选择是什么。

如何在SQL Server中获取两个不同用户的事务来锁定单个记录而不是整个表?

这里是一个快速和肮脏的控制台应用程序,说明问题。我创建了一个名为“test1”的数据库,其中一个名为“Values”的表具有ID(int)和Value(nvarchar)列。如果您运行该应用程序,它会要求修改一个ID,启动一个事务,修改该记录,然后保持事务处于打开状态,直到您按下ENTER键。我希望能够

  1. 启动程序并告诉它更新ID 1;
  2. 让它得到它的交易并修改记录;
  3. 启动程序的第二个副本,并告诉它更新ID 2;
  4. 它能够在第一个应用程序的事务处于打开状态时更新(并提交)。

目前它冻结在第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(); 
    } 
} 

这里有一些事情我已经试过了,在行为没有变化:

  • 更改事务隔离级别为 “读未提交”
  • 指定 “WITH(ROWLOCK)”在UPDATE语句上

回答

5

只是检查,但你对ID列主键或唯一索引?

+0

原来我没有;我使ID成为自动编号字段,但我没有将其作为主键。当我添加主键时,它获得了记录级锁。我不知道为什么主键应该有所作为,但显然它有。谢谢! – 2010-04-16 14:23:17

+1

不客气。我确信有更多的DBA说这种方式,但是AIUI,这意味着你在查找目标记录时避免了表扫描,这就是锁定表的原因。 – 2010-04-16 16:33:54

1

调查乐观与悲观锁定。

编辑: 上一篇文章链接到经典ado ...对不起。

http://msdn.microsoft.com/en-us/library/cs6hb8k4(VS.71).aspx

+0

那页说“如果你的基础数据源支持事务,你可以在一个事务中更新的数据来模拟悲观并发。”所以看起来它对于乐观并发的讨论仅适用于*不*使用事务的情况。 – 2010-04-15 16:00:10

+0

这是帮助吗? http://articles.techrepublic.com.com/5100-10878_11-1049842.html 它讨论了使用“可重复读”选项集创建事务本身。 – Jeremy 2010-04-15 16:12:50

+0

还可以查看 http://www.sql-server-performance.com/articles/per/new_isolation_levels_p1.aspx 了解SQL 2005及更高版本中可用的不同隔离级别。 我相信sql 2000和2005之间的默认隔离级别发生了变化,所以当服务器忙的时候,从一个升级到另一个的应用程序有一些令人讨厌的惊喜。 – Jeremy 2010-04-15 16:20:55

1

可能索引是在行锁被设置为“off”的情况下创建的。
查询中的“WITH(ROWLOCK)”在这种情况下不起作用。

您可以ALTER INDEX它们重新打开,例如:

ALTER INDEX [PK_Values] ON [Values] SET (ALLOW_ROW_LOCKS = ON)