2012-02-29 11 views
13

Ho un programma che si collega a un database Oracle ed esegue operazioni su di esso. Ora voglio adattare quel programma per supportare anche un database SQL Server.In SQL Server, come posso bloccare una singola riga in modo simile a "SELECT FOR UPDATE WAIT" di Oracle?

Nella versione Oracle, utilizzo "SELECT FOR UPDATE WAIT" per bloccare le righe specifiche di cui ho bisogno. Lo uso in situazioni in cui l'aggiornamento è basato sul risultato di SELECT e altre sessioni non possono assolutamente modificarlo contemporaneamente, quindi devono prima bloccarlo manualmente. Il sistema è altamente soggetto a sessioni che tentano di accedere agli stessi dati nello stesso momento.

Ad esempio:
Due utenti tentano di recuperare la riga nel database con la priorità più alta, contrassegnarla come occupata, eseguire operazioni su di essa e contrassegnarla come disponibile di nuovo per un utilizzo successivo. In Oracle, la logica sarebbe andato sostanzialmente in questo modo:

BEGIN TRANSACTION; 
SELECT ITEM_ID FROM TABLE_ITEM WHERE ITEM_PRIORITY > 10 AND ITEM_CATEGORY = 'CT1' 
    ITEM_STATUS = 'available' AND ROWNUM = 1 FOR UPDATE WAIT 5; 
UPDATE [locked item_id] SET ITEM_STATUS = 'unavailable'; 
COMMIT TRANSACTION; 

Nota che le query sono costruite in modo dinamico nel mio codice. Si noti inoltre che quando la riga precedentemente più favorevole è contrassegnata come non disponibile, il secondo utente passerà automaticamente a quello successivo e così via. Inoltre, utenti diversi che lavorano su diverse categorie non dovranno aspettare che i blocchi di ciascuno vengano rilasciati. Il peggiore peggiore, dopo 5 secondi, verrà restituito un errore e l'operazione verrà annullata.

Quindi, infine, la domanda è: come ottengo gli stessi risultati in SQL Server? Ho guardato i suggerimenti di blocco che, in teoria, sembrano come dovrebbero funzionare. Tuttavia, gli unici blocchi che impediscono altri blocchi sono "UPDLOCK" E "XLOCK" che funzionano entrambi a livello di tabella.
Questi suggerimenti di blocco che funzionano a livello di riga sono tutti blocchi condivisi, che inoltre non soddisfano le mie esigenze (entrambi gli utenti possono bloccare la stessa riga allo stesso tempo, contrassegnarli come non disponibili ed eseguire operazioni ridondanti sull'elemento corrispondente).

Alcune persone sembrano aggiungere una colonna "tempo modificato" in modo che le sessioni possano verificare che siano quelle che l'hanno modificata, ma sembra che ci siano molti accessi ridondanti e non necessari.

risposta

7

In SQL Server sono presenti suggerimenti per il blocco ma non si estendono alle loro istruzioni come l'esempio Oracle fornito. Il modo per farlo in SQL Server consiste nell'impostare un livello di isolamento sulla transazione che contiene le istruzioni che si desidera eseguire. Vedere this MSDN page ma la struttura generale sarebbe simile:

SET TRANSACTION ISOLATION LEVEL SERIALIZABLE; 

BEGIN TRANSACTION; 

    select * from ... 

    update ... 

COMMIT TRANSACTION; 

SERIALIZABLE è il più alto livello di isolamento. Vedi il link per altre opzioni. Da MSDN:

SERIALIZABLE specifica i seguenti:

dichiarazioni non possono leggere i dati che sono stati modificati ma non ancora commesso da altre transazioni.

Nessun'altra transazione può modificare i dati letti dalla transazione corrente fino al completamento della transazione corrente.

Altre transazioni non possono inserire nuove righe con valori chiave che vadano nell'intervallo di chiavi lette da qualsiasi istruzione nella transazione corrente fino al completamento della transazione corrente.

+3

E 'bene sapere, ma sembra che Serializable impedirà legge solo se i dati sono stati modificati, in modo da non impedire che le serrature fino a quel momento. – Paradoxyde

+0

@Paradoxyde: non è la mia comprensione. Per la seconda voce della specifica (vedi la mia modifica sopra) la riga viene bloccata non appena viene letta fino alla fine della transazione. Da quale parte della specifica hai ricevuto la tua domanda? –

+4

Questo acquisisce un blocco condiviso e in seguito un blocco esclusivo, quindi è vulnerabile a deadlock – Andomar

1

Hai provato CON (ROWLOCK)?

BEGIN TRAN 

    UPDATE your_table WITH (ROWLOCK) 
    SET your_field = a_value 
    WHERE <a predicate> 

COMMIT TRAN 
+1

ROWLOCK impedisce a due utenti di modificare la stessa riga, ma non impedisce a due utenti di bloccare quella stessa riga. – Paradoxyde

+0

Vedo, grazie per i chiarimenti. – Pablushka

+0

@Paradoxyde Se due persone bloccano la stessa riga, sarà un deadlock. L'aggiornamento della riga avrà successo? –

9

Probabilmente stai cercando with (updlock, holdlock). Questo renderà un select un blocco exclusive, che è necessario per gli aggiornamenti, invece di un blocco shared. L'hint holdlock indica a SQL Server di mantenere il blocco fino al termine della transazione.

FROM TABLE_ITEM with (updlock, holdlock) 
+0

Sono d'accordo che questo è il tipo di blocco che sto cercando, tuttavia, come ho affermato nella domanda, l'aggiornamento ha effetto sull'intera tabella, mentre sto cercando di influenzare solo una singola riga. – Paradoxyde

+3

'updlock' cambia il tipo di blocco (esclusivo vs condiviso) ma non ciò che è bloccato. Per bloccare l'intera tabella, specifica 'with (tablock)', o per bloccare una riga, specifica 'with (rowlock)' – Andomar

1

Come documentation sayed:

XLOCK

Specifica che blocchi esclusivi sono da prendere e mantenuta fino alla transazione completata. Se specificato con ROWLOCK, PAGLOCK o TABLOCK, , i blocchi esclusivi si applicano al livello appropriato di granularità.

Così soluzione utilizza WITH(XLOCK, ROWLOCK):

BEGIN TRANSACTION; 

SELECT ITEM_ID 
FROM TABLE_ITEM 
WITH(XLOCK, ROWLOCK) 
WHERE ITEM_PRIORITY > 10 AND ITEM_CATEGORY = 'CT1' AND ITEM_STATUS = 'available' AND ROWNUM = 1; 

UPDATE [locked item_id] SET ITEM_STATUS = 'unavailable'; 

COMMIT TRANSACTION; 
Problemi correlati