Является ли MERGE атомарным оператором в SQL2008?

Я использую оператор MERGE как UPSERT, чтобы добавить новую запись или обновить текущую. У меня есть несколько потоков, управляющих базой данных через несколько соединений и несколько операторов (одно соединение и оператор на поток). Я группирую заявления по 50 за раз.

Я был очень удивлен, когда во время тестов получил duplicate key нарушение. Я ожидал, что это будет невозможно, потому что MERGE будет выполняться как одна транзакция, или это так?

Мой код Java выглядит так:

private void addBatch(Columns columns) throws SQLException {
  try {
    // Set parameters.
    for (int i = 0; i < columns.size(); i++) {
      Column c = columns.get(i);
      // Column type is an `enum` with a `set` method appropriate to its type, e.g. setLong, setString etc.
      c.getColumnType().set(statement, i + 1, c.getValue());
    }
    // Add the insert as a batch.
    statement.addBatch();
    // Ready to execute?
    if (++batched >= MaxBatched) {
      statement.executeBatch();
      batched = 0;
    }
  } catch (SQLException e) {
    log.warning("addBatch failed " + sql + " thread " + Thread.currentThread().getName(), e);
    throw e;
  }
}

Запрос выглядит так:

MERGE INTO CustomerSpend AS T 
USING ( SELECT ? AS ID, ? AS NetValue, ? AS VoidValue ) AS V 
ON T.ID = V.ID 
WHEN MATCHED THEN 
    UPDATE SET T.ID = V.ID, T.NetValue = T.NetValue + V.NetValue, T.VoidValue = T.VoidValue + V.VoidValue 
WHEN NOT MATCHED THEN 
    INSERT ( ID,NetValue,VoidValue ) VALUES ( V.ID, V.NetValue, V.VoidValue );

Ошибка гласит:

java.sql.BatchUpdateException: Violation of PRIMARY KEY constraint 'PK_CustomerSpend'. Cannot insert duplicate key in object 'dbo.CustomerSpend'. The duplicate key value is (498288              ).
at net.sourceforge.jtds.jdbc.JtdsStatement.executeBatch(JtdsStatement.java:944)
at x.db.Db$BatchedStatement.addBatch(Db.java:299)
...

Ключ в таблице - это PRIMARY ключ в поле ID.


person OldCurmudgeon    schedule 26.03.2012    source источник
comment
Как вы генерируете первичный ключ (V.ID)?   -  person Paolo    schedule 26.03.2012
comment
@ Паоло ALTER TABLE CustomerSpend ADD CONSTRAINT [PK_CustomerSpend] PRIMARY KEY CLUSTERED (ID). Есть ли способ лучше?   -  person OldCurmudgeon    schedule 26.03.2012
comment
Извините, я имел в виду фактическое значение идентификатора, который вы передали в запросе. Микаэль объяснил это ниже - транзакция является атомарной, но нет ничего, что могло бы остановить несколько потоков, пытающихся вставить один и тот же ключ.   -  person Paolo    schedule 27.03.2012


Ответы (1)


MERGE является атомарным, что означает, что либо все изменения фиксируются, либо все изменения откатываются.

Это не предотвращает дублирование ключей в случае высокой степени параллелизма. Об этом позаботится добавление holdlock подсказки.

MERGE INTO CustomerSpend WITH (HOLDLOCK) AS T 
USING ( SELECT ? AS ID, ? AS NetValue, ? AS VoidValue ) AS V 
ON T.ID = V.ID 
WHEN MATCHED THEN 
    UPDATE SET T.ID = V.ID, T.NetValue = T.NetValue + V.NetValue, T.VoidValue = T.VoidValue + V.VoidValue 
WHEN NOT MATCHED THEN 
    INSERT ( ID,NetValue,VoidValue ) VALUES ( V.ID, V.NetValue, V.VoidValue );
person Mikael Eriksson    schedule 26.03.2012
comment
Не могли бы вы рассказать нам, что означает в случае высокой степени параллелизма? - person Pankaj; 30.08.2012
comment
comment
Дополнительная информация о рисках параллелизма: www. mssqltips.com/sqlservertip/3074несколько других проблем с MERGE в T-SQL). - person Elaskanator; 23.10.2019