2

我想保持我的Azure的SQL Server数据库中数据的一致性,实现了两种方案,以保持数据的一致性:天青时SQL Server无法使用检查约束和触发

  1. 检查约束
  2. 插入/更新触发器

没有一个能够工作,而且当我的支票被绕过时,我仍然能够重现这种情况。规则很简单 - there couldn't be more than one active assignment for a user

Tasks: 
- Id 
- UserId 
- Status ('Active', 'Done') 

User 
- Id 
- ... 

方法#1 - Check Constraints

我已经实现的功能,以确保数据的一致性和应用它作为一个检查约束

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 - Trigger

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 

如果我并行创建大量任务(regularl y> 10),那么其中的一些将被约束/触发器拒绝,其他将能够同时将UserId保存在数据库中。因此数据库数据会不一致。

我已经在Management Studio中验证了这两种方法,它可以防止我破坏我的数据。我无法将多个“活动”任务分配给给定的用户。

什么是improtant说,我用Entity Framework 6.x在我的后端,以节省我的数据(SaveChangesAsync)和每一个动作都是在单独的事务用默认事务隔离级别执行保存ReadCommited

什么可能是错误的我的方法以及如何保持我的数据一致?

回答

3

这是一个典型的竞赛条件。

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功能,他们将获得由Tasks相同count。然后,每个线程将继续插入行,并且数据库的最终状态将违反您的约束。

如果您想要使用CHECK约束中的函数或触发器保持您的方法,则应确保您的事务隔离级别设置为SERIALIZABLE。或者使用查询提示来锁定表格。或者使用sp_getapplock来串行调用你的函数/触发器。


在你的情况下,检查非常简单,所以它可以在没有触发器或函数的情况下实现。我会使用一个filtered unique index

CREATE UNIQUE NONCLUSTERED INDEX [IX_UserID] ON [dbo].[Tasks] 
(
    [UserID] ASC 
) 
WHERE (Status = 'Active') 

这种独特的指数将保证没有两行Status = 'Active'具有相同UserID


还有一个关于dba的类似问题。se How are my SQL Server constraints being bypassed?更详细的解释。他们提到了另一种可能的解决方案 - 索引视图,它再次归结为独特的索引。

+0

的作品,这是一个惊喜,我检查约束也受到竞争条件。对我而言,这是一个独特的索引。在我的情况下,我没有义务使用函数或触发器,并且我的条件很简单,所以我使用唯一的过滤索引并且它可以工作。现在我的代码中仍然有例外,但只要数据始终保持一致,就可以很好地处理它们。谢谢! –

+0

实际上,对函数的检查约束变为由两个(或更多步骤)组成的非原子操作。你的函数和触发它的语句('INSERT'或'UPDATE')在相同的事务隔离级别下运行。发动机周围没有“魔法”可以防止竞赛状况。当涉及到独特的(过滤)索引时,引擎具有“魔力”,即使在高并发负载下也能确保索引保持唯一。 –

+0

如果你想进一步研究这个话题,我建议搜索'检查约束udf'。例如:http://dba.stackexchange.com/questions/12779/how-are-my-sql-server-constraints-being-bypassed http://sqlblog.com/blogs/alexander_kuznetsov/archive/2009/07/ 01/when-check-constraints-using-udfs-fail-for-multirow-updates.aspx http://sqlblog.com/blogs/alexander_kuznetsov/archive/2009/06/25/scalar-udfs-wrapped-in-check -constraints-is-very-slow-and-may-fail-for-multirow-updates.aspx https://www.brentozar.com/archive/2016/04/another-hidden-parallelism-killer-scalar-udfs-检查约束/ –