2012-12-03 71 views
4

我有一個表,用於保存用戶操作的計數。每次行動完成後,該值都需要增加。由於用戶可以同時擁有多個會話,因此該過程需要是原子級的,以避免多用戶問題。SQL Server:添加行如果不存在,增加一列的值,原子

表有3列:

  • ActionCodevarchar
  • UserIDint
  • Countint

我想通過ActionCodeUserID到將增加一個功能一個新的行如果還不存在,並將計數設置爲1.如果該行存在,則只會將計數增加1。 ActionCodeUserID構成了此表的主要唯一索引。

如果所有我需要做的是更新的,我可以做一些簡單的像這樣的(因爲UPDATE查詢是原子的話):

UPDATE (Table) 
SET Count = Count + 1 
WHERE ActionCode = @ActionCode AND UserID = @UserID 

我是新來的SQL原子事務。這個問題可能在這裏有很多部分回答,但我很難找到這些問題,並將這些部分放在一個解決方案中。這需要非常快速,而且不會變得複雜,因爲這些操作可能會頻繁發生。

編輯:對不起,這可能是一個騙局MySQL how to do an if exist increment in a single query。我搜索了很多,但在我的搜索中有tsql,一旦我改爲sql,那就是最好的結果。這是不明顯的,如果這是原子,但很確定它會。我可能會投票刪除這個作爲愚蠢的,除非有人認爲這個問題和答案可以增加一些新的價值。

+1

您能否澄清一下您正在使用的RDBMS? –

+0

在頂部添加了編輯,MS SQL Server 2008. – eselk

+0

謝謝。包含「sql-server」和/或「sql-server-2008」標籤通常會有所幫助。 –

回答

8

假設你是SQL Server上,使單個原子語句,你可以使用MERGE

MERGE YourTable AS target 
USING (SELECT @ActionCode, @UserID) AS source (ActionCode, UserID) 
ON (target.ActionCode = source.ActionCode AND target.UserID = source.UserID) 
WHEN MATCHED THEN 
    UPDATE SET [Count] = target.[Count] + 1 
WHEN NOT MATCHED THEN 
    INSERT (ActionCode, UserID, [Count]) 
    VALUES (source.ActionCode, source.UserID, 1) 
OUTPUT INSERTED.* INTO #MyTempTable; 

UPDATE使用輸出如果需要選擇的值。代碼已更新。

+0

儘管它很有吸引力,不適用於大多數數據庫。最好先找出哪些數據庫OP正在使用 – Bohemian

+0

謝謝您添加示例。我可能會在這個鏈接的另一個帖子上使用類似這樣的內容,因爲我也想返回最終的值/計數,並且該部分也是原子的。很高興至少有選擇,所以我想我會保持開放(不刪除或報告欺騙)。 – eselk

+0

@eselk要自動獲取值,只需使用'OUTPUT'子句。查看更新後的答案。 –

2

在SQL Server 2008中使用MERGE可能是最好的選擇。還有另一種簡單的方法來解決它。

如果用戶ID /操作不存在,請對Count執行一個新行的插入操作。如果由於該語句已經存在而導致該語句失敗(就像剛剛由另一個併發會話插入的語句),則只需忽略該錯誤即可。

如果你想在執行它來消除誤差的任何機會做插件和模塊,你可以添加一些鎖提示:

INSERT dbo.UserActionCount (UserID, ActionCode, Count) 
SELECT @UserID, @ActionCode, 0 
WHERE NOT EXISTS (
    SELECT * 
    FROM dbo.UserActionCount WITH (ROWLOCK, HOLDLOCK, UPDLOCK) 
    WHERE 
     UserID = @UserID 
     AND ActionCode = @ActionCode 
); 

然後做+ 1的更新並在通常情況下。問題解決了。

DECLARE @NewCount int, 

UPDATE UAC 
SET 
    Count = Count + 1, 
    @NewCount = Count + 1 
FROM dbo.UserActionCount UAC 
WHERE 
    ActionCode = @ActionCode 
    AND UserID = @UserID; 

注1:合併應該沒問題,但要知道,只是因爲事情是在一個聲明(因此原子)做並不意味着它沒有併發問題。在查詢執行的整個生命週期中,會隨着時間的推移獲取並釋放鎖。像下面這樣的查詢會遇到併發問題,導致重複的ID插入嘗試,儘管是原子性的。

INSERT T 
SELECT (SELECT Max(ID) FROM Table) + 1, GetDate() 
FROM Table T; 

注2:我讀的人的文章經歷了超高的交易容量系統說,他們發現了「嘗試 - 它 - 那麼,手柄任何錯誤」的方法來提供比更高的併發獲取和釋放鎖。這在所有系統設計中可能都不是這樣,但至少值得考慮。我已經搜索了這篇文章幾次(包括剛纔),並且無法再找到它......我希望有一天能夠找到它並重讀它。

+0

問題是關於原子事務,所以至少你需要將這些語句包裝到'BEGIN TRAN'和'END TRAN'中,並確保隔離級別是合適的。在這種情況下,您可能需要在插入之前進行檢查,而不是在錯誤上進行中繼。 –

+0

@SergeBelov我想,如果有人希望INSERT回滾,如果UPDATE失敗,那麼是的,人們將不得不使用BEGIN TRAN和END TRAN。隔離級別並不是真正的問題,因爲無論隔離級別是什麼,您都無法在沒有適當的鎖的情況下執行INSERT或UPDATE。爲了在INSERT語句中獲得適當的鎖定,以便不會發生錯誤,接近我更新我的答案的鎖定將會起作用。 – ErikE

+0

@SergeBelov請記住,我最初的答案並不打算使用任何阻止。如果你需要阻塞,'SERIALIZABLE'對應'HOLDLOCK',但它本身還不夠!我更喜歡不寫代碼,這些代碼結合了在同一查詢中指定鎖定的不同方式(「SET TRANSACTION ISOLATION LEVEL」與「WITH(LOCKHINT)」)。如果我需要HOLDLOCK作爲特定的查詢,我將提示放在查詢上。我並不是想說隔離級別是沒有意義的,但是這對於這裏的目的是不夠的,因此不是真正的最重要的問題。 – ErikE

1

Incase任何人都需要在存儲過程中使用它的語法,並返回插入/更新的行(我很驚訝插入。*還會返回更新的行,但它確實)。這是我最終的結果。我忘記了我的主鍵(ActionKey)中有一個額外的列,它反映在下面。如果你只想返回計數,也可以做「輸出插入計數」,這更實用。

CREATE PROCEDURE dbo.AddUserAction 
(
@Action varchar(30), 
@ActionKey varchar(50) = '', 
@UserID int 
) 
AS 

MERGE UserActions AS target 
USING (SELECT @Action, @ActionKey, @UserID) AS source (Action, ActionKey, UserID) 
ON (target.Action = source.Action AND target.ActionKey = source.ActionKey AND target.UserID = source.UserID) 
WHEN MATCHED THEN 
    UPDATE SET [Count] = target.[Count] + 1 
WHEN NOT MATCHED THEN 
    INSERT (Action, ActionKey, UserID, [Count]) 
    VALUES (source.Action, source.ActionKey, source.UserID, 1) 
output inserted.*; 
相關問題