Является ли MERGE атомарным утверждением в SQL2008? - PullRequest
13 голосов
/ 26 марта 2012

Я использую оператор 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.

1 Ответ

27 голосов
/ 26 марта 2012

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 );
...