Azure Sql Server无法使用检查约束和触发器保持数据一致性
我试图保持Azure Sql Server数据库中的数据一致性,并实现了两种方案:Azure Sql Server无法使用检查约束和触发器保持数据一致性,sql,sql-server,entity-framework,azure-sql-database,transaction-isolation,Sql,Sql Server,Entity Framework,Azure Sql Database,Transaction Isolation,我试图保持Azure Sql Server数据库中的数据一致性,并实现了两种方案: 检查约束 插入/更新触发器 它们都不起作用,当我的支票被绕过时,我仍然能够再现一种情况。规则很简单-一个用户不能有多个活动分配 Tasks: - Id - UserId - Status ('Active', 'Done') User - Id - ... 进近#1-检查约束 我实现了一个确保数据一致性的函数,并将其作为检查约束应用 create function [dbo].[fnCheckUse
一个用户不能有多个活动分配
Tasks:
- Id
- UserId
- Status ('Active', 'Done')
User
- Id
- ...
进近#1-检查约束
我实现了一个确保数据一致性的函数,并将其作为检查约束应用
create function [dbo].[fnCheckUserConcurrentAssignment]
(
@id nvarchar(128),
@status nvarchar(50), -- not used but required to check constraint
)
returns bit
as
begin
declare @result bit
select @result = cast(case when (select count(t.Id)
from dbo.Tasks t
where t.[UserId] = @id
and t.[Status != 'Done') > 1
then 1
else 0
end as bit)
return @result
end
alter table dbo.Tasks
with check add constraint [CK_CheckUserConcurrentAssignment]
check (dbo.fnCheckUserConcurrentAssignment([UserId], [Status]) = 0)
进近#2-触发
alter trigger dbo.[TR_CheckUserConcurrentAssignment]
on dbo.Tasks
for insert, update
as begin
if(exists(select conflictId from
(select (select top 1 t.Id from dbo.Tasks t
where t.UserId = i.UserId
and o.[Status] != N'Done'
and o.Id != i.Id) as conflictId
from inserted i
where i.UserId is not null) conflicts
where conflictId is not null))
begin
raiserror ('Concurrent user assignment detected.', 16, 1);
rollback;
end
end
如果我并行地创建了很多分配(通常>10),那么其中一些分配将被约束/触发器拒绝,另一些分配将能够同时在数据库中保存UserId。因此,由数据库生成的数据将不一致
我已经在ManagementStudio中验证了这两种方法,它可以防止我破坏数据。我无法将多个“活动”任务分配给给定用户
重要的是,我在后端使用实体框架6.x
来保存数据(savechangesync
),每个保存操作都在一个单独的事务中执行,具有默认事务隔离级别readcommitted
我的方法可能有什么错误,以及如何保持数据的一致性?这是一个典型的比赛条件
select @result = cast(case when (select count(t.Id)
from dbo.Tasks t
where t.[UserId] = @id
and t.[Status != 'Done') > 1
then 1
else 0
end as bit)
如果两个线程同时运行fnCheckUserConcurrentAssignment
函数,它们将从任务中获得相同的计数。然后每个线程将继续插入行,数据库的最终状态将违反您的约束
如果您想使用检查约束中的函数或触发器保持您的方法,您应该确保您的事务隔离级别设置为可序列化
。或者使用查询提示锁定表。或者使用sp_getapplock
序列化对函数/触发器的调用
在您的例子中,检查非常简单,因此它可以在没有触发器或函数的情况下实现。我会使用:
此唯一索引将保证没有两个状态为“活动”的行具有相同的用户ID
dba.se上有一个类似的问题,有更详细的解释。他们提到了另一种可能的解决方案-索引视图,它可以再次归结为唯一索引。这是可行的,我感到惊讶的是,检查约束也受到竞争条件的约束。对我来说,这是一个独特的索引。在我的情况下,我没有义务使用函数或触发器,我的条件就是这么简单,所以我使用唯一的过滤索引,它可以工作。现在我的代码中仍然存在异常,但只要数据始终保持一致,就可以优雅地处理它们。谢谢实际上,函数的检查约束变成了由两个(或更多步骤)组成的非原子操作。您的函数在与触发它的语句(INSERT
或UPDATE
)相同的事务隔离级别下运行。它周围的引擎没有“魔法”可以阻止比赛状态。当涉及到唯一(过滤)索引时,引擎具有“魔力”,确保索引即使在高并发负载下也保持唯一。如果您想进一步研究此主题,我建议搜索检查约束udf
。例如:Azure SQL数据库似乎将我的Read Committed
隔离级别(我在代码中明确选择了该级别)更改为Read Committed Snapshot
,我来这里是想说“filtered unique constraint”!
CREATE UNIQUE NONCLUSTERED INDEX [IX_UserID] ON [dbo].[Tasks]
(
[UserID] ASC
)
WHERE (Status = 'Active')