2009-11-05 52 views
7

(注意:這是針對MS SQL Server的)SQL Server競爭條件問題

假設您有一個帶有主鍵標識列和CODE列的表ABC。我們希望這裏的每一行都有一個唯一的,順序生成的代碼(基於一些典型的校驗位公式)。

假設你有另一個只有一行的DEF表,它存儲了下一個可用的CODE(設想一個簡單的自動編號)。

我知道像下面邏輯將呈現競爭狀態,其中兩個用戶可以使用相同的代碼結束:

1) Run a select query to grab next available code from DEF 
2) Insert said code into table ABC 
3) Increment the value in DEF so it's not re-used. 

我知道,兩個用戶可能會卡在步驟1),並能最後在ABC表中使用相同的代碼。

處理這種情況的最佳方法是什麼?我認爲我可以圍繞這個邏輯包裝一個「begin tran」/「commit tran」,但我認爲這並不奏效。我有一個存儲過程,這樣的測試,但我沒有避免競爭狀態,當我從MS兩個不同的窗口跑:

begin tran 

declare @x int 

select @x= nextcode FROM def 

waitfor delay '00:00:15' 

update def set nextcode = nextcode + 1 

select @x 

commit tran 

有人可以提供一些線索這光?我認爲這個事務會阻止另一個用戶在第一個事務完成之前訪問我的NextCodeTable,但我認爲我對事務的理解是有缺陷的。

編輯:我試着移動等待到「更新」語句後,我得到了兩個不同的代碼......但我懷疑這一點。我在那裏等待語句來模擬延遲,因此可以很容易地看到比賽情況。我認爲關鍵問題是我對交易如何運作的錯誤認識。

+0

您應該查看我已故的答案:接受一個不正確... – gbn 2011-04-03 09:30:52

回答

6

將事務隔離級別設置爲可序列化。
在較低的隔離級別上,其他事務可以讀取在該事務中讀取(但尚未修改)的行中的數據。所以兩個事務確實可以讀取相同的值。在非常低的隔離(未提交讀)其他事務,甚至可以讀取數據,它已經修改之後(但在此之前提交)...有關SQL Server隔離級別

評論詳情here

那麼底線是隔離級別在這裏是一個策略性的部分,用於控制其他交易進入這一級別的訪問級別。

注意。來自link,約可串行化
語句不能讀取已被修改但尚未被其他事務提交的數據
這是因爲鎖在行被修改時放置,而不是當Begin Trans發生時,所以您所做的操作可能仍然允許另一個事務讀取舊值直到修改它爲止。所以我會改變邏輯,在你閱讀它的同一個語句中修改它,從而在同一時間將鎖定在它上面。

begin tran 
declare @x int 
update def set @x= nextcode, nextcode += 1 
waitfor delay '00:00:15' 
select @x 
commit tran 
+0

我想你的意思是*更新def set @ x = nextcode,nextcode + = 1 * – 2016-06-23 17:57:41

+0

是的,謝謝! – 2016-06-23 18:18:06

0

您可以將列設置爲持久化的計算值。這將處理比賽條件。

Persisted Computed Columns

注意

使用這種方法意味着你不需要下一個代碼存儲在表中。代碼欄成爲參考點。

實施

爲該列計算的列規範下的以下特性。

公式= dbo.GetNextCode()

持久化=是

Create Function dbo.GetNextCode() 
Returns VarChar(10) 
As 
Begin 

    Declare @Return VarChar(10); 
    Declare @MaxId Int 

    Select @MaxId = Max(Id) 
    From Table 

    Select @Return = Code 
    From Table 
    Where Id = @MaxId; 

    /* Generate New Code ... */ 

    Return @Return; 

End 
+0

在函數體,你如何訪問的最後一個(即堅持)價值? – 2009-11-05 22:28:49

+0

這真的取決於表格的設計,但我給出的更新示例應該涵蓋大多數情況。 – ChaosPandion 2009-11-05 22:41:31

+0

對不起 - 應該在第一條評論中提出兩個問題!下一個代碼如何被持久化,或者在哪裏? – 2009-11-05 22:53:31

3

回顧:

  • 你開始交易。這實際上不會「做」任何事情,它會修改後續行爲
  • 您從表中讀取數據。默認的隔離級別是Read Committed,所以這個select語句是而不是作爲事務的一部分。
  • 然後您等待15秒
  • 然後您發出更新。使用已聲明的事務,這將在事務提交之前生成一個鎖。
  • 然後您提交交易,釋放鎖定。

所以,猜測你在兩個窗口(A和B)跑這同時:

  • 一個讀取表中的「下一個」值閃避,然後進入等待模式
  • 體B讀出的表中相同的「下一個」值,然後進入等待模式。 (由於A只做了一次讀取,交易沒有鎖定任何東西。)
  • A然後更新了表,並可能在B退出等待狀態之前提交了更改。
  • B在A的寫入提交後更新了表。

嘗試在更新之後,提交之前放置wait語句,看看會發生什麼。

0

這實際上是SQL數據庫中的一個常見問題,這就是爲什麼大多數(所有?)都有一些內置功能來處理獲取唯一標識符的問題。如果您使用的是Mysql或Postgres,則需要查看一些內容。如果你使用不同的數據庫,我敢打賭,提供的東西非常相似。

的一個很好的例子是,你可以看看這裏的Postgres序列:

Postgres Sequences

MySQL使用一種叫做自動遞增。

Mysql auto increment

1

這不是一個真正的競賽條件。這是併發事務更常見的問題。一種解決方案是在表上設置一個讀鎖,並因此具有序列化。

5

至於其他的反應已經提到的,你可以設置事務隔離級別,以確保你「讀」使用SELECT語句在一個事務中不能改變的事情。

或者,你可以拿出鎖專門對DEF表加入語法WITH HOLDLOCK表名後,如

SELECT nextcode FROM DEF WITH HOLDLOCK 

它並沒有太大的區別就在這裏,因爲你的交易是小,但是對於某些SELECT而不是事務中的其他人取出鎖可能很有用。這是'可重複性與併發性'的問題。

一些相關的MS-SQL文檔。