我正在尝试了解 SQL Server 版本存储和相关的隔离级别。据我了解,当数据库启用读取提交快照选项时,可能会发生这种情况:
- 一个项目 (id = 1) 在数据库中的价格为 $1000
- 会话 1 启动更新语句:
update products set price = price * 1.5
. 由于这涉及表格的所有行,因此需要很长时间。 - 当
update
语句仍在进行中时,会话 2 开始查询:select * from products where id = 1
。由于数据库处于读取提交快照模式,因此写入者不会阻塞读取者。所以会话 1 从版本商店读取了该行的旧版本,并认为该产品的价格为 1000 美元。 - session 1的用户觉得价格还不错,所以决定买。但 ...
- 在用户将产品添加到他的购物车之前,上述
update
语句执行完毕,产品 (id = 1) 的新价格为 1500 美元。如果用户知道产品的新价格,他就不会购买。
在这种情况下,会发生什么?这种情况真的可能吗?如果是这样,防止这种情况的规范是什么?
“在用户将产品添加到他的购物车之前......如果用户知道产品的新价格,他就不会购买。 ” - 这些是发生在应用程序端的事情,与数据库无关,因此从数据库的角度来看,很难回答围绕它们的问题类型。
是的,一点没错。
这取决于您如何编写应用程序。如果应用程序总是从数据库中提取最新数据,因为用户还没有将商品添加到购物车,那么在将商品添加到购物车后,他们将看到 1,500 美元的最新价格。如果您正在使用某种异步应用程序代码,那么您甚至可以在用户当前所在的屏幕上自动更新价格,以便他们在将商品添加到购物车之前看到最新价格。但同样,这些都是独立于数据库层的应用层决策。
使用悲观的隔离级别,例如 SQL Server 默认的 Read Committed,这将阻止用户在更新完成之前看到价格。
这实际上是您的两个选项(来自数据库层),要么 1,000 美元(乐观隔离级别)的价格是应用程序查询数据库时的正确价格,要么 1,500 美元(悲观隔离级别)是当时的正确价格。并且使用乐观的隔离级别,更新发生的时间没有什么不正确的,这样价格在用户第一次看到价格之后发生变化。这只是事件的顺序。
从某种意义上说,它有点像 eBay,商品的价格仅在用户查看该价格时才有效。在之后的任何时刻(例如当用户去购买或竞标该物品时)该价格可能会更改或过期(因为拍卖物品是有时间限制的)。
您所指的问题称为write skew,并且在使用乐观并发读取和写入数据时发生。
是的,这在 下是绝对可能的
SNAPSHOT
,因为版本化数据没有被锁定,并且可以在该隔离级别下读取。这与实际执行的隔离级别无关update products ...
,因为正在读取的会话只会转到版本存储。为了解决这个问题,一种常见的解决方案是在进行新写入时确认假设(对于实际预订)。新的写入也需要在非
SNAPSHOT
隔离级别下,并且必须至少是REPEATABLE READ
级别(或者您可以一起READCOMMITTEDLOCK
使用 a和UPDLOCK
table 提示)。因此,您将使用 读取您想要用于 UI 目的的数据
SNAPSHOT
,这允许出于 UI 目的进行非阻塞读取。然后在实际创建预订时,您将执行以下操作:此方法的另一个变体是返回相关
rowversion
行的值,然后检查(使用与上面相同的事务语义)rowversion
是否相同。如果配置,这就是实体框架等 ORM 所做的。