9

我有一个记录用户操作的表。每完成一个动作,价值就需要增加。由于用户可以同时拥有多个会话,因此该进程需要是原子的以避免多用户问题。

该表有 3 列:

  • ActionCode作为varchar
  • UserID作为int
  • Count作为int

我想将ActionCodeand传递UserID给一个函数,如果一个新行不存在,它将添加一个新行,并将计数设置为 1。如果该行确实存在,它只会将计数增加一。 ActionCodeUserID构成该表的主唯一索引。

如果我需要做的只是更新,我可以做一些简单的事情(因为UPDATE查询已经是原子的):

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

我是 SQL 中原子事务的新手。这个问题可能已经在这里的多个部分得到了回答,但我很难找到这些部分并将这些部分放在一个解决方案中。这也需要非常快,不要变得复杂,因为这些操作可能会经常发生。

编辑:对不起,这可能是MySQL 如何在单个查询中执行 if exists increment的欺骗。我搜索了很多,但tsql在我的搜索中,一旦我改为sql改为,那是最好的结果。这是否是原子的并不明显,但可以肯定的是。我可能会投票删除这个作为欺骗,除非有人认为这个问题和答案可以增加一些新的价值。

4

3 回答 3

10

假设您在 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;

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

于 2012-12-03T23:03:23.093 回答
3

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

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

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

注 2:我在超高事务量系统中经验丰富的人员阅读的一篇文章说,他们发现“try-it-then-handle-any-error”方法提供比获取和释放锁更高的并发性。这可能并非在所有系统设计中都是如此,但至少值得考虑。此后我多次(包括刚刚)搜索过这篇文章,但无法再次找到它......我希望有一天能找到它并重读它。

于 2012-12-03T23:21:52.453 回答
1

万一其他人需要语法在存储过程中使用它并返回插入/更新的行(我很惊讶 insert.* 也返回更新的行,但确实如此)。这就是我最终得到的。我忘记了我的主键(ActionKey)中有一个额外的列,它反映在下面。如果只想返回Count,也可以做“输出插入.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.*;
于 2012-12-04T05:28:16.950 回答