MERGE是SQL 2008中的原子语句吗?

时间:2012-03-26 11:58:10

标签: java sql sql-server-2008 upsert

我使用MERGE语句作为UPSERT来添加新记录或更新当前记录。我有多个线程通过多个连接和多个语句驱动数据库(每个线程一个连接和语句)。我一次批量处理这些陈述。

我在测试期间遇到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 个答案:

答案 0 :(得分:27)

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