SQL Server:如果不存在则添加行,增加一列的值,原子

时间:2012-12-03 22:59:49

标签: sql sql-server sql-server-2008 atomic

我有一个表,用于记录用户操作。每次执行操作时,值都需要增加。由于用户可以同时拥有多个会话,因此该过程需要是原子的,以避免多用户问题。

该表有3列:

  • ActionCodevarchar
  • UserIDint
  • Countint

我想将ActionCodeUserID传递给一个函数,该函数将添加一个新行(如果尚不存在),并将count设置为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,那就是最好的结果。如果这是原子的,那不是很明显,但很确定它会是。我可能会投票删除这个作为欺骗,除非有人认为这个问题和答案可以增加一些新的价值。

3 个答案:

答案 0 :(得分:9)

假设您使用的是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;

更新如有必要,请使用输出选择值。代码已更新。

答案 1 :(得分:3)

在SQL Server 2008中使用MERGE可能是最好的选择。还有另一种简单的方法可以解决它。

如果UserID / Action不存在,请对Count的0行执行新行的INSERT。如果由于它已经存在而导致该语句失败(正好由另一个并发会话插入),则只需忽略该错误。

如果您想在执行插入和阻止时消除任何错误,可以添加一些锁定提示:

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:MERGE应该没问题,但要知道只是因为在一个语句中完成了某些事情(因此是原子的)并不意味着它没有并发问题。在查询执行的整个生命周期中,随时间获取和释放锁。像下面这样的查询将遇到并发问题,导致重复的ID插入尝试,尽管是原子的。

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

注意2:我在超高交易量系统中遇到的人所读到的一篇文章称他们发现“try-it-then-handle-any-error”方法提供更高的并发性而不是获取和释放锁。在所有系统设计中可能并非如此,但至少值得考虑。我已经多次搜索过这篇文章(包括刚才)并且再也找不到了...我希望有一天能找到它并重读它。

答案 2 :(得分:1)

包含其他任何人需要语法在存储过程中使用它并返回插入/更新的行(我很惊讶插入。*也返回更新的行,但确实如此)。这就是我最终的结果。我忘了我的主键(ActionKey)中有一个额外的列,它反映在下面。如果你只想返回Count,也可以做“output inserted.Count”,这更实用。

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.*;