2009-06-15 68 views
2

我正在使用一個應用程序,該應用程序根據滿足特定條件的最後一行向數據庫添加新行。有沒有處理這類問題的標準模式,還是我只需要鎖定表格?添加行時需要數據庫併發性 - 最佳實踐?

下面是一個過於簡單化的可視化:

A1 
A2 
A3 
B1 
B2 

使用上面,一個web頁面加載到最高的「B」值,該值是「2」的可視化。然後,經過一段時間,它想要插入 B3,該系列中的下一個記錄。但是,它必須檢查以確保別人沒有做同樣的事情。

就像我提到的,我知道我可以讀取事務內的期望值,或者我可以鎖定表,或者甚至可以鎖定最後一行。我問的是如果有推薦的策略。

+0

如果B3已經存在,該怎麼辦?錯誤,什麼都不做或插入B4?沒有這個,我們不能真正回答... – gbn 2009-06-15 17:59:16

+0

什麼都不做。該應用程序的設計非常糟糕,現在它會插入B4。您可以繼續點擊提交,也可以提交併繼續移動設備。 – 2009-06-15 19:13:13

回答

1

見我的博客如何做到這一點使用遞歸CTE的和此項目的單一IDENTITY

更新:

如果問題是將設備建設到下一步,那麼你可能更好地使用絕對值而不是相對值。

記住變量中的前一個值(在頁面本身或服務器端),並用變量的新值更新它。

取而代之的是:

UPDATE mytable 
SET  step = step + 1 

使用本:

SET @nextstep = 2 
UPDATE mytable 
SET  step = @nextstep 

您也可以包含自動增量last_update字段添加到列,以確保您要更新,因爲一欄沒有更新您的頁面已加載:

SELECT last_update 
INTO @lastupdate 
FROM mytable 
WHERE item_id = @id 

UPDATE mytable 
SET  step = @nextstep 
WHERE item_id = @id 
     AND last_update = @lastupdate 

更新2

如果您使用的鏈接狀態列表(即,即你不更新,但插入新的狀態),那麼就標誌着列IDENTITY並插入先前狀態的ID

item_id step_id prev_step_id 
1  10232  0 
1  12123  10232 

,使step_idprev_step_id獨一無二的,這樣的查詢:

WITH q (item_id, step_id, step_no) AS 
     (
     SELECT item_id, step_id, 1 
     FROM mytable 
     WHERE item_id = 1 
       AND prev_step_id = 0 
     UNION ALL 
     SELECT q.item_id, m.step_id, q.step_no + 1 
     FROM q 
     JOIN mytable m 
     ON  m.item_id = q.item_id 
       m.prev_step_id = q.step_id 
     ) 
SELECT * 
FROM q 

如果兩個人想要插入兩條記錄,那麼prev_step_id上的UNIQUE約束將失敗,並且最後一個插入將失敗。

+0

這就是爲什麼我提到我的例子太簡單了。實際上,每行對應於一臺設備在工廠訪問的地方。該頁面(不容易重寫),目前將設備移動到下一步。問題是,如果兩個人在加載頁面後按下相同的按鈕,則設備可以移動兩個步驟。我需要檢查以確保以前的位置符合預期的位置。 – 2009-06-15 17:41:19

+0

唯一約束的好主意! – 2009-06-15 17:58:17

+0

@SuperJason:將成爲我博客中今日文章的主題。謝謝你的一個很好的問題! – Quassnoi 2009-06-15 17:59:22

0

這是使用隊列的最佳例子。試用Message Broker。

-1
UPDATE yourtable 
    SET location = 'B3' 
WHERE primary-key = 1231421 
    AND location = 'B2' 

如果有人已經將它從B2中移出,那麼什麼都不會發生。這似乎比簡單地盲目增加位置要好;用戶希望它從B2轉到B3,而不是向前推。

好吧,考慮到新行要求:

INSERT INTO yourtable (item, location) VALUES(123, 'B3') 
WHERE NOT EXISTS(SELECT * FROM yourtable WHERE item = 123 AND location = B3) 

讓數據庫做的工作適合你。

-1

通常的做法是創建一個rowversion類型的列,並使用從客戶端傳入的值檢查行值。 如果表中的行已更新,則rownumbers將不匹配。 索引rowversion列,它會飛。

1

這看起來像我經常需要的操作符,我總是希望:「確保滿足這些條件的元組存在,並給我關鍵。」

在我的情況下,通常是一個簡單的「我有這個信用卡號碼和有效日期,關鍵是什麼?」我實際上並不關心它是否已經在數據庫中(事實上,出於安全目的,應用程序不應該能夠說出),我只是想要標識符,如果它在那裏,或者我希望它是如果不是,則創建並獲取該創建的新標識符。

據我所知,使用當前的DBMS技術,您需要鎖定表格,因爲您必須基於已存在的內容決定是否插入。但是,我希望有更好的解決方案。

0

我知道你給出的例子是簡化的,但爲了滿足這些要求,你可以做到以下幾點。

由於INSERT/SELECT是一個語句,它隱含在事務中。如果你需要做一些你無法表達的東西,你需要將它包裝在一個明確的事務中。

主鍵可確保您的給定缺省事務隔離範圍之外不存在併發問題。

CREATE TABLE Sequence 
(
    [Name] char(1), 
    [Seq] int 
    PRIMARY KEY (Name, Seq) 
) 
GO 
CREATE PROCEDURE Sequence_Insert 
(
    @name char(1) 
) 
AS 
INSERT INTO Sequence(Name, Seq) 
SELECT 
    @Name, 
    COALESCE(MAX(Seq),0) + 1 
FROM 
    Sequence 
WHERE 
    Name = @Name 
GO 
exec Sequence_Insert 'A' 
exec Sequence_Insert 'A' 
exec Sequence_Insert 'B' 
exec Sequence_Insert 'A' 
exec Sequence_Insert 'C' 
GO 
SELECT * FROM Sequence 
0

只需將其包裝到相同的語句。新值(2)的第一個插入將成功,第二個將添加零行。

create table t1 (i int) 
insert t1 values (1) 

insert t1 (i) 
    select 2 where exists (select max(i) from t1 having max(i) = 1) 

insert t1 (i) 
    select 2 where exists (select max(i) from t1 having max(i) = 1) 
1

概念,我的理解澄清的積累,情況要記錄項目已進入了一個新的狀態 - 一臺設備已經達到了一定的一步。並且要根據遞增目前據信是在一步做到這一點。

我想重申這也許是更易於管理和明確的。你能簡單地插入一條記錄,斷言機器在一個狀態中被觀察到,並帶有時間戳嗎?

推導從前面的信息的當前步驟(即本身可以不完全已知)似乎有風險的,特別是如果它是一個可以從0發生基於情況n倍簡單的迭代計算。如果它是一個時間標記的實際狀態觀察,那麼它是自我糾正的(不管你以前認爲它的狀態如何),並且多重斷言不會導致問題。

你能基於現有的表單重新構建邏輯嗎(或者可能是對錶單或網絡配置等的小修改)?是否存在與步驟子集中給定步驟關聯的用戶或IP地址等?是否有關聯的交易只在步驟或步驟子集中有效?

0

適當的策略取決於在讀取B2和插入B3之間發生的操作究竟是什麼。以下幾點是相當理論性而不實用的T-SQL示例,但我認爲這是您的問題。

  • 如果這是一次性的結果便宜的動作,那麼你可以讓每一筆交易做了,那麼插入B3第一個成功,其餘的失敗,重複鍵衝突(唯一約束),從異常中恢復和像沒有任何事情一樣恢復。
  • 一次性結局相對昂貴的手術,但不太可能同時發生。與上述相同,您將承擔處置「昂貴」操作結果的懲罰,但這種情況很少會發生。
  • 一個可以回滾的非一次性結果(可以嵌入到您的交易中,或者您可以註冊到DTC中)並且不太可能同時發生。與上述相同,但在您的事務(本地或DTC)中註冊「操作」時,如果發生衝突,請使用Bs序列的新值重試。
  • 結果是非一次性的(「操作」的結果不能被忽略,它必須被記錄下來,例如你記錄了一筆金融交易)。在這種情況下,您必須防止併發,並且鎖定是一條路。表鎖總是矯枉過正,你應該在'B2'後查找UPDLOCK。不幸的是事務隔離級別在這裏沒有任何幫助(在級別的所有級別上,兩個線程都可以讀取'B2'並向前衝刺,導致希望在插入時出現死鎖)。如果讀取B2和插入B3之間的「操作」與將HTML返回給用戶並等待下一個POST相當複雜,那麼您可能無法承擔在實際數據上留下如此長壽的U鎖,而最好的方法是使用sp_getapplock來設備應用程序鎖定模式。
0

推薦的SQL策略肯定是使用SELECT FOR UPDATE。我很驚訝沒有人提到它。

 
SELECT id FROM tasks WHERE id = max(id) FOR UPDATE OF tasks; 

SELECT FOR UPDATE鎖只是正是你需要的鎖,所以它比手動鎖定要簡單得多。