Почему сервер SQL работает так, что MERGE вставляет несколько записей? - PullRequest
0 голосов
/ 25 марта 2020

У меня есть 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? Как я могу предотвратить это?

1 Ответ

2 голосов
/ 25 марта 2020

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/

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...