Базы данных

Как выполнить upsert в SQL Server: MERGE и альтернативы

Узнайте, как надёжно выполнить upsert в SQL Server с помощью MERGE, INSERT…SELECT…WHERE NOT EXISTS и других подходов, учитывая производительность и блокировки.

In SQL Server, how can I efficiently perform an upsert—updating a row if the key exists or inserting a new row if it does not, you know—on a table defined as MyTable(KEY, datafield1, datafield2, ...)? Well, what is the most performant approach for this pattern?

A single statement that updates if the key exists and inserts otherwise—known as an upsert—is most efficiently handled with the MERGE statement in SQL Server, in fact. For high‑concurrency workloads, wrap the MERGE in a transaction and use the OUTPUT clause only when you need to return affected rows, in fact. When you need tighter control over locking or want to avoid the overhead of MERGE on very large tables, a two‑step INSERT … SELECT … WHERE NOT EXISTS inside a transaction with SERIALIZABLE isolation can be a faster alternative, in fact.


Contents


Краткое описание

MERGE позволяет одновременно сравнить строки источника и целевой таблицы по ключу, обновляя существующие записи и вставляя новые. Самый быстрый способ в большинстве сценариев – использовать MERGE с минимальными индексационными затратами и учётом изоляции транзакций.


Таблица ключевых подходов

Подход Когда использовать Основные преимущества Основные недостатки
MERGE Условное обновление/вставка в одной операции Одно обращение к диску, простая логика Возможные проблемы блокировок, сложности с логированием
INSERT … SELECT … WHERE NOT EXISTS Высокая конкуренция, требуется контроль над блокировками Уровень изоляции SERIALIZABLE гарантирует отсутствие фальшивых конфликтов Два прохода по данным, возможно более медленно при больших таблицах
TRY…CATCH + INSERT/UPDATE Требуется откат при конфликте Позволяет детально обрабатывать ошибки Двойная запись, риск потерять обновление
Bulk staging + MERGE Массовые загрузки Позволяет обрабатывать батчи, уменьшая нагрузку Требует дополнительного пространства и синхронизации

Понимание MERGE

sql
MERGE INTO MyTable AS target
USING (VALUES (@Key, @Data1, @Data2)) AS source (Key, Data1, Data2)
ON target.Key = source.Key
WHEN MATCHED THEN
    UPDATE SET
        target.Data1 = source.Data1,
        target.Data2 = source.Data2
WHEN NOT MATCHED THEN
    INSERT (Key, Data1, Data2)
    VALUES (source.Key, source.Data1, source.Data2);

Ключевые моменты:

  • Индексы – наличие уникального индекса по Key обеспечивает быструю проверку существования.
  • OUTPUT – если нужно вернуть изменённые строки, можно добавить OUTPUT $action, inserted.*, deleted.*.
  • ТранзакцииMERGE обычно выполняется в собственном автокоммит‑контексте; при необходимости оберните в BEGIN TRAN/COMMIT.

Ссылки

  • According to the official Microsoft documentation, MERGE is supported from SQL Server 2008 and onward.
  • A performance guide on SQL Server Central discusses indexing strategies for MERGE operations.

Альтернативные шаблоны

1. INSERT … SELECT … WHERE NOT EXISTS

sql
BEGIN TRANSACTION;

INSERT INTO MyTable (Key, Data1, Data2)
SELECT @Key, @Data1, @Data2
WHERE NOT EXISTS (SELECT 1 FROM MyTable WITH (UPDLOCK, HOLDLOCK) WHERE Key = @Key);

IF @@ROWCOUNT = 0
BEGIN
    UPDATE MyTable
    SET Data1 = @Data1,
        Data2 = @Data2
    WHERE Key = @Key;
END;

COMMIT TRANSACTION;
  • UPDLOCK + HOLDLOCK (индикатор SERIALIZABLE) предотвращают «фальшивые» вставки/обновления.
  • Двойной проход по таблице, но блокировки ограничены одной строкой, что уменьшает конкуренцию при больших таблицах.

2. TRY…CATCH с INSERT и UPDATE

sql
BEGIN TRY
    INSERT INTO MyTable (Key, Data1, Data2) VALUES (@Key, @Data1, @Data2);
END TRY
BEGIN CATCH
    IF ERROR_NUMBER() = 2627  -- уникальный ключ
        UPDATE MyTable SET Data1 = @Data1, Data2 = @Data2 WHERE Key = @Key;
END CATCH;
  • Позволяет обрабатывать только реальный конфликт, но двойная запись может быть медленнее при высокой частоте конфликтов.

3. Массовый staging + MERGE

  1. Загружаем данные во временную таблицу #Staging.
  2. Выполняем MERGE один раз для всех строк.
sql
MERGE INTO MyTable AS target
USING #Staging AS source
ON target.Key = source.Key
WHEN MATCHED THEN UPDATE SET ...
WHEN NOT MATCHED THEN INSERT (...);
  • Снижает количество логических операций, но требует дополнительного пространства.

Проблемы производительности и их решение

Проблема Причина Решение
Блокировки и блокирующие запросы MERGE может удерживать блокировки на целевой таблице дольше, чем INSERT/UPDATE. Установите уровень изоляции READ COMMITTED SNAPSHOT или используйте ROWLOCK/UPDLOCK в MERGE.
Низкая пропускная способность при массовых вставках Частые обращения к диску и индексация. Выполняйте MERGE в батчах (например, 10 000 строк за раз) и отключайте AUTO_UPDATE_STATISTICS.
Проблемы с индексацией Несовместимые индексы могут замедлять обновление. Создайте только необходимые индексы; для больших таблиц используйте filtered indexes по часто обновляемым столбцам.
Проблемы с логированием MERGE генерирует более сложные записи в журнале транзакций. Если журнал слишком заполнен, уменьшите размер CHECKPOINT или используйте simple режим, но только в тестовых средах.

Ссылки

  • Microsoft’s guidance on transaction isolation levels and locking hints.
  • SQL Server Central article on optimizing MERGE for bulk operations.

Рекомендованный подход

Сценарий Лучший шаблон Почему
Небольшие или средние таблицы, низкая конкуренция MERGE Одно обращение, простая логика.
Высокая конкуренция, тонкая настройка блокировок INSERT … SELECT … WHERE NOT EXISTS с SERIALIZABLE Гарантирует отсутствие конфликтов, ограничивает блокировки.
Массовая загрузка (млн+ строк) staging + MERGE Снижает количество логических операций, ускоряет обработку.
Требуется возврат измененных строк MERGE с OUTPUT Позволяет вернуть INSERTED/DELETED.

Итого: для большинства бизнес‑приложений MERGE в сочетании с правильным индексом по ключу и уровнем изоляции READ COMMITTED обеспечивает наилучшее соотношение простоты и производительности. При критических нагрузках на запись применяйте INSERT … SELECT … WHERE NOT EXISTS в транзакции с SERIALIZABLE или batch‑обработку через staging‑таблицу.


Заключение

  • MERGE – самая простая и обычно самая быстрая реализация upsert.
  • INSERT … SELECT … WHERE NOT EXISTS с блокировками UPDLOCK/HOLDLOCK подходит, когда важна минимизация конфликтов.
  • Batch‑MERGE через staging‑таблицу эффективен при больших объёмах данных.
  • Убедитесь, что у таблицы есть уникальный индекс по ключу; индексация остальных колонок должна быть сбалансированной.
  • Управляйте уровнем изоляции и блокировками – это ключ к предотвращению блокировок и повышению пропускной способности.

Источники

  1. Microsoft Docs – MERGE (Transact‑SQL).
  2. SQL Server Central – Optimizing MERGE for Bulk Operations.
  3. Microsoft Docs – Transaction Isolation Levels.
Авторы
Проверено модерацией
Модерация