我有以下表格(此处以实体框架为模型,但我的问题与 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也是如此,两个事务中都没有两个查询。
所以我有两个问题:
- 我的分析正确吗?
- 需要什么隔离级别来避免此类问题?