我有一个表,用于记录用户操作。每次执行操作时,值都需要增加。由于用户可以同时拥有多个会话,因此该过程需要是原子的,以避免多用户问题。
该表有3列:
ActionCode
为varchar
UserID
为int
Count
为int
我想将ActionCode
和UserID
传递给一个函数,该函数将添加一个新行(如果尚不存在),并将count设置为1.如果该行确实存在,它将只是将计数增加一。 ActionCode
和UserID
构成此表的主要唯一索引。
如果我需要做的只是更新,我可以做这样简单的事情(因为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
,那就是最好的结果。如果这是原子的,那不是很明显,但很确定它会是。我可能会投票删除这个作为欺骗,除非有人认为这个问题和答案可以增加一些新的价值。
答案 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.*;