2011-12-13 59 views
1

我們有一個鍵字段的表,其中包含密鑰序列,即當前值另一個表,插入你需要一個新的記錄:SQL Server的更新/選擇鍵順序

UPDATE seq SET key = key + 1 
SELECT key FROM seq 
INSERT INTO table (id...) VALUES (@key...) 

今天,我一直在調查碰撞,並已發現不使用交易上面的代碼並行運行引起的衝突,但是,換了UPDATESELECT線不會引起衝突,即:

SELECT key + 1 FROM seq 
UPDATE seq SET key = key + 1 
INSERT INTO table (id...) VALUES (@key...) 

任何人都可以解釋,爲什麼? (我對更好的方式不感興趣,我將使用事務,並且我不能更改數據庫設計,我只是對爲什麼觀察我們所做的事情感興趣。)

我正在運行兩條線的SQL作爲單個字符串,使用C#的SqlConnection,SqlCommandSqlDataAdapter

+1

爲什麼不使用Auto_Increment? SQL Sever將其保存在緩衝區中,因此不存在衝突。 –

+0

@ key被分配了一個值在哪裏? – Glenn

+0

@BrianHoover正如我在問題中所說的,我無法更改數據庫設計。 –

回答

2

首先,您的查詢並不完全合理。這就是我想你實際上是這樣做的:

UPDATE seq SET key = key + 1 
SELECT @key = key FROM seq 
INSERT INTO table (id...) VALUES (@key...) 

SELECT @key = key + 1 FROM seq 
UPDATE seq SET key = @key 
INSERT INTO table (id...) VALUES (@key...) 

您遇到綁事務隔離級別的併發問題。

事務隔離級別表示需要併發(即,性能),並需要對數據質量(即精度)之間的折衷。

默認情況下,SQL使用讀提交的隔離級別,這意味着你不能讓「髒」讀(已被其他事務修改數據的讀取,但尚未提交到表)。它確實是而不是,但是,這意味着您不受其他類型的併發問題的困擾。

在你的情況下,你遇到的問題被稱爲不可重複讀取。

在第一個例子中,第一行是讀取鍵值,然後更新它。 (爲了讓UPDATE將列設置爲鍵+ 1,它必須首先讀取鍵的值)。然後第二行的SELECT再次讀取鍵值。在Read Committed或Read Uncommitted隔離級別中,有可能另一個事務同時完成鍵字段的更新,這意味着第2行將其讀取爲鍵+ 2而不是預期鍵+ 1。現在

,與你的第二個例子,一旦密鑰值已讀取和修改,並放置在@key變量,它沒有被再次讀取。這可以防止不可重複的讀取問題,但您仍然完全不受併發問題的困擾。在這種情況下會發生什麼是丟失的更新,其中兩個或多個事務最終嘗試將密鑰更新爲相同的值,隨後將重複密鑰插入到表中。

絕對肯定具有作爲設計的這個結構沒有併發問題,你需要使用鎖提示,以確保所有讀取和更新,關鍵是序列化(即非併發)。這將會有可怕的表現,但是「WITH UPDLOCK,HOLDLOCK」將會讓你有所收穫。

如果您不能更改數據庫設計,最好的解決方案是找到可以的人。正如Brian Hoover所指出的那樣,一個自動遞增的IDENTITY列是以卓越的性能實現這一目標的方法。現在你這樣做的方式將SQL的V-8引擎減少到只允許在一個柱面上觸發的引擎。

+0

謝謝。如果你做了一個INSERT,然後SELECT @@ IDENTITY,你總能得到正確的值嗎?那裏沒有比賽條件? –

+0

是和不是。實際上有3種獲取身份識別值的方法 - IDENT_CURRENT,@@ IDENTITY和SCOPE_IDENTITY - 每種方法在不同情況下都適用。這不是一個競爭條件問題,而是範圍問題。我建議研究微軟的示例代碼,比較三者:http://msdn.microsoft.com/en-us/library/ms175098.aspx –