2009-01-30 7 views
34

Vorrei aggiornare un set di righe in base a un criterio semplice e ottenere l'elenco di PK modificati. Ho pensato che avrei potuto solo fare qualcosa di simile, ma sono preoccupato per possibili problemi di concorrenza:C'è un modo per selezionare e aggiornare le righe allo stesso tempo?

SELECT Id FROM Table1 WHERE AlertDate IS NULL; 
UPDATE Table1 SET AlertDate = getutcdate() WHERE AlertDate IS NULL; 

Se questo è avvolto in una transazione ci sono eventuali problemi di concorrenza che possono verificarsi? O c'è un modo migliore per farlo?

risposta

65

Considerare lo OUTPUT clause capability of UPDATE (anche DELETE e INSERT). Esempio dalla pagina MSDN collegata:

UPDATE TOP (10) HumanResources.Employee 
SET VacationHours = VacationHours * 1.25, 
    ModifiedDate = GETDATE() 
OUTPUT inserted.BusinessEntityID, 
     deleted.VacationHours, 
     inserted.VacationHours, 
     inserted.ModifiedDate 
INTO @MyTableVar; 
+0

Si applica a "AGGIORNAMENTO IMPOSTATO ... DA ... DOVE ....'? – Kiquenet

8

Sarebbe più semplice eseguire prima l'UPDATE e quindi eseguire "SELECT ID FROM INSERTED".

Dai un'occhiata a SQL Tips per maggiori informazioni ed esempi.

0

se è all'interno della transazione, il sistema di chiusura del database si prenderà cura di problemi di concorrenza. naturalmente, se ne usi uno (il default di mssql è che usa il lock, quindi indica se non lo sostituisci)

0

Modifica: il mio male, volevi la selezione per mostrare i risultati dopo l'aggiornamento, non aggiornare da una selezione.

Hai provato una sottoselezione?

update mytable set mydate = sysdate 
where mydate in (select mydate from mytable where mydate is null); 
12

Un modo per gestire questa situazione è quello di farlo in una transazione, e rendere la vostra query SELECT prendere un blocco di aggiornamento sulle righe selezionate fino al completamento della transazione.

BEGIN TRAN 

SELECT Id FROM Table1 WITH (UPDLOCK) 
WHERE AlertDate IS NULL; 

UPDATE Table1 SET AlertDate = getutcdate() 
WHERE AlertDate IS NULL; 

COMMIT TRAN 

Ciò elimina la possibilità che un client concorrente aggiorni le righe selezionate nel momento tra SELECT e UPDATE.

Quando si esegue il commit della transazione, verranno rilasciati i blocchi di aggiornamento.

Un altro modo per gestire ciò è dichiarare un cursore per SELECT con l'opzione FOR UPDATE. Quindi AGGIORNA DOVE CORRENTE DEL CURSORE. Quanto segue non è testato, ma dovrebbe darti l'idea di base:

DECLARE cur1 CURSOR FOR 
    SELECT AlertDate FROM Table1 
    WHERE AlertDate IS NULL 
    FOR UPDATE; 

DECLARE @UpdateTime DATETIME 

SET @UpdateTime = GETUTCDATE() 

OPEN cur1; 

FETCH NEXT FROM cur1; 

WHILE @@FETCH_STATUS = 0 
BEGIN 

    UPDATE Table1 AlertDate = @UpdateTime 
    WHERE CURRENT OF cur1; 

    FETCH NEXT FROM cur1; 

END 
+3

+1 per UPDLOCK, è una soluzione corretta a questo problema e nelle transazioni brevi non porterà a deadlock –

+0

Ciò comporterà che tutte le righe ottengano lo stesso valore datetime (come in una singola chiamata SQL)? In caso contrario, è necessario ottenere il tempo prima del ciclo in una variabile e impostare semplicemente AltertDate sulla variabile. Se si tratta di un problema, modificarlo. – Thorsten

+1

+1 per UPDLOCK e -1 per CURSOR. – jsuddsjr

3

Forse qualcosa di più simile a questo?

declare @UpdateTime datetime 

set @UpdateTime = getutcdate() 

update Table1 set AlertDate = @UpdateTime where AlertDate is null 

select ID from Table1 where AlertDate = @UpdateTime 
+1

Questo ha il vantaggio che, anche se non lo fai all'interno di una transazione, sarà * molto * difficile ottenere risultati alterati da altri processi. –

0

in SQL 2008 una nuova dichiarazione TSQL viene introdotto "Merge" che svolge inserire, aggiornare o eliminare le operazioni su una tabella di destinazione sulla base dei risultati di un join con una tabella di origine. È possibile sincronizzare due tabelle inserendo, aggiornando o eliminando le righe in una tabella in base alle differenze rilevate nell'altra tabella.

http://blogs.msdn.com/ajaiman/archive/2008/06/25/tsql-merge-statement-sql-2008.aspx http://msdn.microsoft.com/en-us/library/bb510625.aspx

6

Molti anni dopo ...

La risposta accettata di usare la clausola OUTPUT è buono.Ho dovuto scavare la sintassi reale, ecco che è:

DECLARE @UpdatedIDs table (ID int) 
UPDATE 
    Table1 
SET 
    AlertDate = getutcdate() 
OUTPUT 
    inserted.Id 
INTO 
    @UpdatedIDs 
WHERE 
    AlertDate IS NULL; 

AGGIUNTO 14 Settembre 2015: "Posso usare una variabile scalare invece di una variabile di tabella"

si potrebbe chiedere ... Scusa, ma no non puoi. Dovrai SELECT @SomeID = ID from @UpdatedIDs se hai bisogno di un singolo ID.

1

Ho affrontato lo stesso problema; Devo aggiornare l'ammontare del credito, e devo ottenere il tempo modificato, insieme con i dettagli del credito da DB. Si tratta essenzialmente di

contemporaneamente/atomicamente compio (UPDATE poi GET) in MYSQL

Ho provato molte opzioni e trovato uno che ha risolto il mio problema.

1) option_1 SELECT PER AGGIORNAMENTO

Questa è mantenere il blocco fino aggiornamento (SYNC da GET a UPDATE), ma ho bisogno di bloccare dopo l'aggiornamento fino al GET.

2) OPTION_2 stored procedure

stored procedure non verrà eseguito in modo sincrono come Redis lua, Quindi non abbiamo bisogno anche del codice di sincronizzazione per eseguire questo.

3) OPTION_3 Transaction

ho usato JPA entityManager come qui di seguito, pensavano che prima di commettere nessuno può aggiornare, e prima di commettere mi metterò l'oggetto aggiornato con il tempo modificato (da DB). Ma non ho ricevuto l'ultimo oggetto. Solo commit ho ottenuto l'ultimo.

try { 
     entityManager.getTransaction().begin(); 
     //entityManager.persist(object); 
     int upsert = entityManager.createNativeQuery(
     "update com.bill.Credit c set c.balance = c.balance - ?1 
      where c.accountId = ?2 and c.balance >= ?1").executeUpdate(); 
      //c.balance >= ? for limit check 
     Credit newCredit = entityManager.find(Credit.class, "id"); 
     entityManager.refresh(newCredit); //SHOULD GET LATEST BUT NOT 
     entityManager.getTransaction().commit(); 
    } finally {  
     entityManager.unwrap(Session.class).close(); 
    } 

4) OPTION_4 LOCK ha risolto il problema, quindi prima dell'aggiornamento ho acquisito il blocco; quindi dopo aver ottenuto il blocco ho rilasciato.

private Object getLock(final EntityManager entityManager, final String Id){ 

    entityManager.getTransaction().begin(); 
    Object obj_acquire = entityManager.createNativeQuery("SELECT GET_LOCK('" + Id + "', 10)").getSingleResult(); 
    entityManager.getTransaction().commit(); 
    return obj_acquire; 
} 


private Object releaseLock(final EntityManager entityManager, final String Id){ 

    entityManager.getTransaction().begin(); 
    Object obj_release = entityManager.createNativeQuery("SELECT RELEASE_LOCK('" + Id + "')").getSingleResult(); 
    entityManager.getTransaction().commit(); 
    return obj_release; 
} 
+0

*** MySQL ***? La domanda è contrassegnata per 'SQL SERVER' – Kiquenet

+0

OHH MY BAD, Answer è per MySQL –