1

我有以下表格(此处以实体框架为模型,但我的问题与 EF 无关):

在此处输入图像描述

如您所见,这是一个版本化Product表。该Id列是主键,但组合(EntityId, VersionId)也可以是主键。EntityId表示实体的 ID,它在实体的不同版本之间是恒定的。通过用 写入一行来删除实体IsDeleted = 1

负责数据操作的存储过程首先检查数据操作是否正常。例如,UPDATE SP 检查实体是否已被删除。如果这些检查成功,SP 在版本表中生成一个新行,然后在表中生成一个新行Product

(pseudo-code):

sp_Product_Update:
(1) IF EXISTS (SELECT Id FROM Product WHERE IsDeleted = 1 AND EntityId = @ProductId)
        RAISERROR "Entity has already been deleted"
        RETURN
(2) INSERT INTO Versions ...
(3) INSERT INTO Product ... (IsDeleted = 0)

sp_Product_Delete:
(1) IF EXISTS (SELECT Id FROM Product WHERE IsDeleted = 1 AND EntityId = @ProductId)
        RAISERROR "Entity has already been deleted"
        RETURN
(2) INSERT INTO Versions ...
(3) INSERT INTO Product ... (IsDeleted = 1)

这一切都很好。

目前,我正在分析这个并发问题。想象一下以下并发场景,其中两个 SP 被同时调用,用于同一个实体:

Transaction 1                          Transaction 2
sp_Product_Update                      sp_Product_Delete

(1) Check succeeds, entity has not yet been deleted.

                                       (1) Same check.

                                       (2) INSERT INTO Versions...
                                       (3) INSERT INTO Product.. (IsDeleted = 1)

(2) INSERT INTO Versions...
(3) INSERT INTO Product ... (IsDeleted = 0)

如您所见,这种竞争条件会导致数据不一致,即在条目之后出现IsDeleted = 0一行。IsDeleted = 1

所以我们必须确定我们需要什么级别的隔离来避免这种竞争条件。

  • 这似乎不是脏读,因为在 (1) 中读取的数据不是脏的。
  • 它也不是不可重复读取,任何一个事务中都没有两次读取。
  • Phantom Read也是如此,两个事务中都没有两个查询。

所以我有两个问题:

  • 我的分析正确吗?
  • 需要什么隔离级别来避免此类问题?
4

1 回答 1

1

您的解决方案需要可序列化的隔离级别,因为所有命令都需要作为一个原子操作一起执行。

如果您不使用存储过程,我会鼓励使用为这种高吞吐量情况设计的乐观锁定。

于 2013-08-12T20:55:33.963 回答