2012-03-26 3 views
13

Sto utilizzando una dichiarazione MERGE come UPSERT per aggiungere un nuovo record o aggiornare quello corrente. Ho più thread che guidano il database attraverso più connessioni e più istruzioni (una connessione e una dichiarazione per thread). Sto mescolando le dichiarazioni 50 alla volta.MERGE è una dichiarazione atomica in SQL2008?

Sono stato molto sorpreso di ottenere una violazione duplicate key durante i miei test. Mi aspettavo che fosse impossibile perché il MERGE sarà eseguito come una singola transazione, o è?

codice

mio Java assomiglia:

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

La query è simile al seguente:

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

L'errore si legge:

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

La chiave sul tavolo è una chiave PRIMARY nel campo ID.

+0

Come si genera la chiave primaria (V.ID)? – Paolo

+0

@Paolo 'ALTER TABLE CustomerSpend ADD CONSTRAINT [PK_CustomerSpend] PRIMARY KEY CLUSTERED (ID)'. C'è un modo migliore? – OldCurmudgeon

+0

Mi spiace, intendevo il valore effettivo dell'ID che stavi passando nella query. Mikael lo ha capito qui sotto: la transazione è atomica, ma non c'è nulla che impedisca più thread cercando di inserire la stessa chiave – Paolo

risposta

26

MERGE è atomico, significa che tutte le modifiche sono state eseguite o tutte le modifiche sono state annullate.

Non impedisce le chiavi duplicate in caso di concorrenza elevata. L'aggiunta del suggerimento holdlock si occuperà di ciò.

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

Grazie! Ciò ha senso. – OldCurmudgeon

+0

Puoi dirci qual è il significato di "* in caso di concorrenza elevata *"? – Pankaj

+1

@abcdefghi [Esecuzioni simultanee dell'istruzione di fusione] (http://en.wikipedia.org/wiki/Concurrency_ (computer_science)). –