了解 SQL Server 版本存储

Faj*_*iya 5 sql-server isolation-level tempdb-version-store snapshot-isolation

我试图了解 SQL Server 版本存储和相关的隔离级别。据我了解,当数据库启用读取已提交快照选项时,可能会发生这种情况:

  • 数据库中的商品 (id = 1) 价格为 1000 美元
  • 会话 1 开始一条更新语句:update products set price = price * 1.5。由于这涉及表的所有行,因此需要很长时间。
  • update语句仍在进行时,会话 2 启动一个查询:select * from products where id = 1。由于数据库处于读已提交快照模式,因此写入者不会阻止读取者。因此,会话 1 从版本存储中读取该行的旧版本,并认为该产品的价格为 1000 美元。
  • 会话1的用户觉得价格还不错,所以决定购买。但 ...
  • 在用户将产品添加到购物车之前,上述update语句执行完毕,产品(id = 1)的新价格为 1500 美元。如果用户知道产品的新价格,他就不会购买。

在这种情况下,会发生什么呢?这种情况实际上可能吗?如果是这样,防止这种情况的规范是什么?

Cha*_*ace 4

您所指的问题称为写入偏差,当使用乐观并发来读取和写入数据时会发生这种情况。

是的,这在 下绝对是可能的SNAPSHOT,因为版本化数据没有被锁定并且可以在该隔离级别下读取。这与实际执行操作时使用的隔离级别无关update products ...,因为正在读取的会话将仅进入版本存储。

为了解决这个问题,一种常见的解决方案是在进行新写入(针对实际预订)时确认假设。新的写入也需要处于非SNAPSHOT隔离级别下,并且必须至少为REPEATABLE READ级别(或者可以将aREADCOMMITTEDLOCKUPDLOCK表提示一起使用)。

因此,您可以使用 读取您想要用于 UI 目的的数据SNAPSHOT,这允许用于 UI 目的的非阻塞读取。然后,在实际创建预订时,您将执行以下操作:

SET TRANSACTION ISOLATION LEVEL REPEATABLE READ;

SET XACT_ABORT, NOCOUNT ON;

BEGIN TRAN;

IF @price <> (
    SELECT p.price
    FROM products p
    WHERE p.id = @productId
)
    THROW 50001, 'New price detected', 1;


INSERT OrderDetail (....)
VALUES (....);

COMMIT;
Run Code Online (Sandbox Code Playgroud)

此方法的另一个变体是返回rowversion相关行的值,然后检查(使用与上面相同的事务语义)是否rowversion相同。如果配置的话,这就是诸如实体框架之类的 ORM 所做的事情。