У меня есть upsert, реализованный с помощью оператора MERGE в хранимой процедуре в Microsoft SQL Server 2017 Standard Edition.
Проблема в том, что я получаю несколько вставок при одновременных вызовах хранимой процедуры , Я могу воспроизвести поведение, используя JMeter с множеством параллельных потоков. JMeter запускает веб-приложение Java, которое вызывает хранимую процедуру с использованием JDB C. После удаления всех строк и запуска JMeter он часто создает только 1 строку, но иногда создает 2 или более строк. Я думаю, я видел, что это создает до 6 строк.
Я думал, что это невозможно, используя MERGE. Все ответы на этот вопрос говорят, что транзакция не нужна: Нужно ли инкапсулировать один оператор слияния (с вставкой, удалением и обновлением) в транзакции?
В принципе, я хочу таблицу хранить значение максимального размера (LQ_SIZE) для каждого дня вместе со временем (LQ_TIMESTAMP), когда этот максимальный размер произошел. Я делаю две немного необычные вещи в моем упоре. 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 имеет атоми c? Как я могу предотвратить это?