UPDATE 语句行为

Viv*_*tra 8 sql-server concurrency isolation-level update

我有一个关于 SQL Server 的 UPDATE 语句的内部工作原理的问题。我试图了解如果同时收到或服务以下 2 个更新语句会发生什么:

 |    Session 1 - Statement 1 | Session 2 - Statement 2
 |   -------------------------+-------------------------
 |    update dbo.Table1       |  update dbo.Table1 
 |    set Value = 10          |  set Value = 20 
 |    where ID = 1            |  where ID = 1 
 ?    and Value = 0;          |  and Value = 0;
(t)
Run Code Online (Sandbox Code Playgroud)

据我了解,更新将首先选择需要使用共享锁更新的行,这意味着两个更新语句都可以选择特定行。然后它请求一个排它锁来更新列值。因此,结果似乎将 Value 设置为 20。

我错过了什么还是我的理解正确?

连接的隔离级别设置为 READ UNCOMMITTED。

Pau*_*ite 13

我有一个关于 SQL Server 的 UPDATE 语句的内部工作原理的问题。

该语句描述了对数据库的所需逻辑更改。运行时物理上发生的事情取决于执行计划、数据库的状态以及其他并发运行的语句/查询正在做什么。

我提到这一点是因为您的问题与实现细节非常相关。

据我了解,更新将首先选择需要使用共享锁更新的行,这意味着两个更新语句都可以选择特定行。

这通常不会发生。

在大多数隔离级别(包括READ UNCOMMITTED问题中指定的)下,SQL ServerU在定位要更新的行时将采用更新 ( ) 锁。这是添加的一个实现细节,以提供一些针对常见形式的转换死锁的保护(这并不总是足够的,但它确实有帮助)。

注意:SQL Server 可以选择任何方便的访问方法来定位要更新的行。例如,它可能选择使用非聚集索引。更新仍将最终更新基表和任何适用的二级索引,但操作顺序可能不同。

这意味着U锁不一定像人们预期的那样序列化。例如,问题中的一个更新语句可能选择通过二级索引定位记录,在这种情况下,该索引中的条目U应用了锁。没有什么可以阻止第二个更新语句选择使用扫描基表(堆或集群)来定位记录的计划。

在这种情况下,第二次更新可以获得U基表中行的锁,而第一次更新持有U链接到相同基表行的二级索引中的行的锁。

在另一种情况下,SQL Server 可能能够选择单个运算符更新计划(例如,如果 上存在聚集索引(ID, Value))。在这种情况下,会X立即对聚集索引进行排他锁 - 没有先前的U锁。

然后它请求排他锁来更新列值。

数据更改操作总是X在更改之前锁定。这个锁一直保持到事务结束。它可能涉及也可能不涉及将当前持有的U锁转换为X.

例如,如果要更新的行是使用基表定位的,U则将X在基表更新操作符中保存并转换为。如果该行是使用二级索引定位的,U则保留在该索引上,并X在基表上获取新锁。两者UX都在不同的资源上同时举行。

因此,结果似乎将 Value 设置为 20。

根据时间和两个更新语句中的每一个选择的执行计划,有许多可能的结果。仅选择一些更有趣的:

场景一:

  1. 会话 1U在读取时获取基表行上的锁。
  2. 会话 2 阻塞U在基表的同一行上等待获取。
  3. 会话 1 将 Value 设置为 10 并提交。
  4. 会话 2 获得了它的U锁并且发现无事可做,因为现在 Value != 0。

结果:值设置为 10。

场景2:

  1. 会话 2U在读取时获取基表行上的锁。
  2. 会话 1 阻塞等待U在基表中的同一行上获取。
  3. 会话 2 将 Value 设置为 20 并提交。
  4. 会话 1 获得了它的U锁并且发现无事可做,因为现在 Value != 0。

结果:值设置为 20。

场景3:

  1. 会话 1 获取U二级索引的锁(定位要更新的行)。
  2. 会话 2 获取U基表上的锁(定位要更新的行)。
  3. 会话 1 需要X在基表行上获取以执行更新,但被会话 2 持有的锁阻塞U
  4. 会话 2 将其U在基表上的锁定转换为X并将 Value 设置为 20。
  5. 会话 2 现在需要维护二级索引(假设该索引包含 Value 列)。
  6. 会话 2 需要X在二级索引行上获取以更新它,但被会话 1 持有的锁阻塞U
  7. 僵局。其中一个会话因错误回滚;另一个成功完成。

结果:不确定。值可能最终为 10 或 20。


这些只是一些可能性。试图预测详细的引擎锁定行为通常是错误的(在我看来)。而是专注于为要进行的更改编写正确的逻辑规范,并使用提供所需保证的隔离级别。

问题的编写方式让我觉得您可能真的在问“丢失的更新”。如果是这种情况,请在提出更具体的新问题之前,像以下一样查看现有的问答:


Aar*_*and 8

说到写作,没有“同一时间”这样的东西。一个会话将“赢得”锁的竞争,并首先更新值;“失败者”将更新下一个值,而下一个读者将反映其更改。

顺便说一句,隔离级别在这里没有任何影响(除非有包含其他语句的显式事务),并且无论如何READ UNCOMMITTED对 DML 没有意义。为什么要在连接级别设置?你的恐怖故事还不够多吗?你需要更多吗?还有更多