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-檢查約束/ –