3

我试图了解 SQL Server 中的 ROWLOCK 以在锁定记录后更新记录。这是我的观察,并想确认 ROWLOCK 是否像表或页面锁之类的东西,或者我没有正确尝试。ROWLOCK 应该是对行的锁定,而不是对表或页面的锁定。

这是我尝试过的:

我创建了一个简单的表:row_lock_temp_test有两列IDName,没有 PK 或索引。现在我打开 SQL Server,两个不同的客户端但相同的凭据并尝试执行一组查询,如下所示:

客户 1:

1: BEGIN TRANSACTION;
2: update row_lock_temp_test set name = 'CC' where id = 2
3: COMMIT

客户 2:

1: BEGIN TRANSACTION;
2: update row_lock_temp_test set name= 'CC' where id = 2
3: COMMIT

我在 C-1 上执行了查询 1、2,然后转到 C-2 并执行相同的查询,两个客户端都执行了查询,然后我提交了事务,一切都很好。

然后我添加了 RowLock 来更新查询,

C-1

  1: BEGIN TRANSACTION;
  2: update row_lock_temp_test WITH(rowlock) set name = 'CC' where id = 2
  3: COMMIT

C-2

1: BEGIN TRANSACTION;
2: update row_lock_temp_test WITH(rowlock) set name = 'CC' where id = 2
3: COMMIT

现在,我在 C-1 上执行了查询 1 和 2,然后转到 C-2 并尝试执行相同的 2 个查询,但是由于该行被 C-1 锁定,所以它应该在队列中,直到事务在 C-1 上提交。一旦我在 C-1 上提交了事务,对 C-2 的查询就被执行了,然后我也在 C-2 上提交了事务。都好。

在这里,我尝试了另一种场景来执行同一组查询,其中行 id = 3

C-2

 1: BEGIN TRANSACTION;
 2: update row_lock_temp_test WITH(rowlock) set name = 'CC' where id = 3
 3: COMMIT

我在 C-1 中执行了第一个两个查询,然后执行了 C-2 的第一个两个查询,两个客户端的行 ID 不同,但是 C-2 中的查询仍然卡住了。这意味着在使用 id = 2 更新查询时,它已锁定页面或表,我期待行锁定,但它似乎是页面或表锁定。

我还尝试使用 xlock、HOLDLOCK 和 UPDLOCK 和不同的组合,但它总是锁定表。有没有可能只锁定一行。

选择和插入按预期工作。

提前致谢。

4

1 回答 1

4

锁定提示只是提示。您不能“强制”SQL 获取特定类型的锁。

您可以通过以下查询查看正在使用的锁:

select      tl.request_session_id,
            tl.resource_type,
            tl.request_mode,
            tl.resource_description,
            tl.request_status
from        sys.dm_tran_locks   tl
join        sys.partitions      pt  on  pt.hobt_id = tl.resource_associated_entity_id
join        sys.objects         ob  on  ob.object_id = pt.object_id
where       tl.resource_database_id = db_id()
order by    tl.request_session_id

好的,让我们在 SSMS 查询窗口中运行一些代码:

create table t(i int, j int);
insert t values (1, 1), (2, 2);

begin tran;
update t with(rowlock) set j = 2 where i = 1;

打开第二个 SSMS 窗口,然后运行:

begin tran;
update t with(rowlock) set j = 2 where i = 2;

第二次执行将被阻止。为什么?

在第三个窗口中运行锁定查询,注意有两行 a resource_typeof RID,一行 a statusof "grant",另一行 a statusof "wait"。我们马上就会谈到这RID一点。另外,查看resource_description这些行的列。这是相同的值。

好的,那是什么resource_description?这取决于resource_type. 但是对于我们RID来说它代表的是:文件id,然后是页面id,然后是行id(也称为slot)。但是为什么两个执行都锁定了行槽 0?他们不应该尝试锁定不同的行吗?毕竟,我们正在更新不同的行。

David Browne 给出了答案:为了找到要更新的正确行,SQL 必须扫描整个表,因为没有索引告诉它在哪里有多少行i = 1。它会在扫描时对每一行进行更新锁定。为什么每行都需要更新锁?好吧,可以这么说,这不是“做”更新。这将需要一个排他锁。更新锁几乎总是用来防止死锁。

因此,第一个查询已经扫描了所有行,U对每一行都进行了锁定。当然,它在 slot 0 中找到了它想要立即更新的行,并获得了X锁。它仍然有那个X锁,因为我们还没有提交。

然后我们开始第二个查询,它还必须扫描所有行以找到它想要的行。它一开始试图U锁定第一行,但被阻止了。我们第一个查询的X锁正在阻塞它。

因此,您会看到,即使使用行锁定,您的第二个查询仍然被阻止。

好的,让我们回滚查询,看看如果我们让第一个查询更新第二行,第二个查询更新第一行会发生什么?那样有用吗?没有!因为 SQL 仍然无法知道有多少行与谓词匹配。所以第一个查询在槽 0 上获取更新锁,发现它不需要更新它,在槽 1 上获取更新锁,查看 的正确值i,获取排他锁,然后等待我们提交。

查询 2 出现,在插槽 0 上获取更新锁,看到它想要的值,获取它的排他锁,更新值,然后尝试在插槽 1 上获取更新锁,因为它可能也有它想要的值.

您还将在下一个“级别”(即页面)上看到“意向锁”。该操作让引擎的其余部分知道它可能希望在将来的某个时间将锁升级到页面级别。但这不是这里的一个因素。页面锁定不会导致问题。

这种情况下的解决方案?在列上添加索引i。在这种情况下,这可能是主键。然后,您可以按任一顺序进行更新。在这种情况下要求行锁定没有区别,因为 SQL 不知道有多少行与谓词匹配。但是,即使您尝试在某些情况下强制执行行锁定,即使使用主键或适当的索引,SQL 仍然可以选择升级锁定类型,因为锁定整个页面或整个页面会更有效表,而不是锁定和解锁单个行。

于 2020-07-12T08:20:36.623 回答