我在Microsoft SQL Server 2017 Standard Edition的存储过程中使用MERGE语句实现了upsert。
问题是,在对存储过程进行并发调用时,我得到了多个插入。我可以使用具有许多并发线程的JMeter重现该行为。 JMeter命中了一个Java Web应用程序,该应用程序使用JDBC调用存储过程。删除所有行并运行JMeter后,它通常仅创建1行,但有时会创建2个或更多行。我想我已经看到它最多创建6行。
我以为使用MERGE,这是不可能的。这个问题的答案都说交易是不必要的:Is neccessary to encapsulate a single merge statement (with insert, delete and update) in a transaction?
[基本上,我希望表存储每天的最大大小(LQ_SIZE)值,以及最大大小发生的时间(LQ_TIMESTAMP)。我在upsert中做两件不寻常的事情。 1.我要匹配转换为日期的时间戳,所以我将忽略该时间插入或更新该行。 2.我的WHEN MATCHED子句具有AND条件,因此仅当新大小大于当前大小时才更新行。
这是我的表和带有MERGE语句的存储过程:
CREATE TABLE LOG_QUEUE_SIZE (
LQ_APP_ID SMALLINT NOT NULL,
LQ_TIMESTAMP DATETIME2,
LQ_SIZE INT
);
GO
CREATE PROCEDURE LOG_QUEUE_SIZE (
@P_TIMESTAMP DATETIME2,
@P_APP_ID SMALLINT,
@P_QUEUE_SIZE INT
)
AS
BEGIN
-- INSERT or UPDATE the max LQ_QUEUE_SIZE for today in the LOG_QUEUE_SIZE table
MERGE
LOG_QUEUE_SIZE target
USING
(SELECT @P_APP_ID NEW_APP_ID, @P_TIMESTAMP NEW_TIMESTAMP, @P_QUEUE_SIZE NEW_SIZE) source
ON
LQ_APP_ID=NEW_APP_ID
AND CAST(NEW_TIMESTAMP AS DATE) = CAST(LQ_TIMESTAMP AS DATE) -- Truncate the timestamp to the day
WHEN MATCHED AND NEW_SIZE > LQ_SIZE THEN -- Only update if we have a new max size for today
UPDATE
SET
LQ_TIMESTAMP = NEW_TIMESTAMP,
LQ_SIZE = NEW_SIZE
WHEN NOT MATCHED BY TARGET THEN -- Otherwise insert the new size
INSERT
(LQ_APP_ID,
LQ_TIMESTAMP,
LQ_SIZE)
VALUES
(NEW_APP_ID,
NEW_TIMESTAMP,
NEW_SIZE);
END
似乎使用事务处理(在合并处带有BEGIN TRAN...COMMIT
,可以防止该问题,但是性能很糟糕。
如果MERGE是原子的,为什么我会得到多个插入物?我该如何预防?
MERGE,导致多个SQL语句,这意味着它可能导致可能的并发冲突。您应该实现锁定:
MERGE dbo.TableName WITH (HOLDLOCK) AS target
USING ... AS source ...;
https://www.mssqltips.com/sqlservertip/3074/use-caution-with-sql-servers-merge-statement/