为什么 SQL 服务器 upsert 与 MERGE 插入多条记录?
Why is SQL Server upsert with MERGE inserting multiple records?
我在 Microsoft SQL Server 2017 标准版的存储过程中使用 MERGE 语句实现了更新插入。
问题是当我对存储过程进行并发调用时,我得到了多个插入。我能够使用具有大量并发线程的 JMeter 重现该行为。 JMeter 命中一个 Java 网络应用程序,该应用程序使用 JDBC 调用存储过程。在删除所有行和 运行 JMeter 后,它通常只创建 1 行,但有时会创建 2 行或更多行。我想我已经看到它最多创建 6 行。
我认为使用 MERGE 是不可能的。这个问题的答案都说事务是不必要的:
Is neccessary to encapsulate a single merge statement (with insert, delete and update) in a transaction?
基本上,我希望 table 存储每天的最大大小 (LQ_SIZE) 值,以及最大大小出现的时间 (LQ_TIMESTAMP)。我在 upsert 中做了两件稍微不寻常的事情。 1. 我匹配投射到日期的时间戳,所以我插入或更新当天的行而忽略时间。 2. 我的 WHEN MATCHED 子句有一个 AND 条件,所以它只在新大小大于当前大小时更新行。
这是我的 table 和带有 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
使用事务(在 MERGE 周围使用 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/
我在 Microsoft SQL Server 2017 标准版的存储过程中使用 MERGE 语句实现了更新插入。
问题是当我对存储过程进行并发调用时,我得到了多个插入。我能够使用具有大量并发线程的 JMeter 重现该行为。 JMeter 命中一个 Java 网络应用程序,该应用程序使用 JDBC 调用存储过程。在删除所有行和 运行 JMeter 后,它通常只创建 1 行,但有时会创建 2 行或更多行。我想我已经看到它最多创建 6 行。
我认为使用 MERGE 是不可能的。这个问题的答案都说事务是不必要的: Is neccessary to encapsulate a single merge statement (with insert, delete and update) in a transaction?
基本上,我希望 table 存储每天的最大大小 (LQ_SIZE) 值,以及最大大小出现的时间 (LQ_TIMESTAMP)。我在 upsert 中做了两件稍微不寻常的事情。 1. 我匹配投射到日期的时间戳,所以我插入或更新当天的行而忽略时间。 2. 我的 WHEN MATCHED 子句有一个 AND 条件,所以它只在新大小大于当前大小时更新行。
这是我的 table 和带有 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
使用事务(在 MERGE 周围使用 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/