2013-02-27 14 views
5

Possiedo un database Oracle a cui accedo utilizzando Devart e Entity Framework.Lettura e aggiornamento simultanei in una tabella di database

C'è un tavolo chiamato IMPORTJOBS con una colonna STATUS.

Ho anche più processi in esecuzione allo stesso tempo. Ognuno di loro legge la prima riga in IMPORTJOBS che ha lo stato 'REGISTERED', lo mette nello stato 'EXECUTING' e, se fatto, lo mette nello stato 'EXECUTED'.

Ora, perché questi processi sono in esecuzione in parallelo, credo che la seguente potrebbe accadere:

  • processo Una legge fila 10, che ha lo status di REGISTERED,
  • processo B si legge inoltre fila 10 che ha ancora lo status REGISTERED ,
  • processo Una riga di aggiornamento 10 allo stato EXECUTING.

Il processo B non dovrebbe essere in grado di leggere la riga 10 mentre il processo A lo ha già letto e sta per aggiornarne lo stato.

Come dovrei risolvere? Metti letto e aggiorna in una transazione? O dovrei usare un approccio di versioning o qualcos'altro?

Grazie!

EDIT: grazie alla risposta accettata, l'ho ottenuto funzionante e l'ho documentato qui: http://ludwigstuyck.wordpress.com/2013/02/28/concurrent-reading-and-writing-in-an-oracle-database.

risposta

2

È necessario utilizzare i meccanismi di blocco incorporati del database. Non reinventare la ruota, soprattutto dal momento che gli RDBMS sono progettati per il per gestire la concorrenza e la coerenza.

In Oracle 11g, suggerisco di utilizzare la funzione SKIP LOCKED. Ad esempio ogni processo potrebbe chiamare una funzione come questa (supponendo id sono numero):

CREATE OR REPLACE TYPE tab_number IS TABLE OF NUMBER; 

CREATE OR REPLACE FUNCTION reserve_jobs RETURN tab_number IS 
    CURSOR c IS 
     SELECT id FROM IMPORTJOBS WHERE STATUS = 'REGISTERED' 
     FOR UPDATE SKIP LOCKED; 
    l_result tab_number := tab_number(); 
    l_id number; 
BEGIN 
    OPEN c; 
    FOR i IN 1..10 LOOP 
     FETCH c INTO l_id; 
     EXIT WHEN c%NOTFOUND; 
     l_result.extend; 
     l_result(l_result.size) := l_id; 
    END LOOP; 
    CLOSE c; 
    RETURN l_result; 
END; 

Ciò restituirà 10 righe (se possibile) che non sono bloccati. Queste righe saranno bloccate e le sessioni non si bloccheranno a vicenda.

In 10g e prima da quando Oracle restituisce risultati coerenti, utilizzare saggiamente FOR UPDATE e non si dovrebbe avere il problema che si descrive. Per esempio si consideri il seguente SELECT:

SELECT * 
    FROM IMPORTJOBS 
WHERE STATUS = 'REGISTERED' 
    AND rownum <= 10 
FOR UPDATE; 

Che cosa accadrebbe se tutti i processi si riservano il file con questo SELEZIONA? In che modo ciò influirà sul tuo scenario:

  1. La sessione A ottiene 10 righe che non vengono elaborate.
  2. La sessione B ottiene le stesse 10 righe, è bloccata e attende la sessione A.
  3. Sessione A aggiorna gli stati delle righe selezionate e ne conferma la transazione.
  4. Oracle eseguirà (automaticamente) di nuovo la selezione della sessione B dall'inizio poiché i dati sono stati modificati e abbiamo specificato FOR UPDATE (questa clausola forza Oracle ad ottenere l'ultima versione del blocco).
    Ciò significa che la sessione B riceverà 10 nuove righe.

Quindi, in questo scenario, non ci sono problemi di coerenza. Inoltre, supponendo che la transazione per richiedere una riga e cambiare il suo stato sia veloce, l'impatto della concorrenza sarà leggero.

+0

grazie, sto cercando di eseguire "SELECT * FROM IMPORTJOBS WHERE STATUSCODE = 'REGISTRATO' E ROWNUM <= 1 PER AGGIORNA SALTA BLOCCATO", ma sta ancora restituendo la stessa riga dai diversi processi? –

+1

(1) assicurarsi di aver disattivato l'autocommit: non è possibile bloccare una riga senza una transazione. (2) 'FOR UPDATE SKIP LOCKED' e' rownum' [non funzionerà come previsto] (http://stackoverflow.com/questions/5847228/oracle-select-for-update-behaviour) - questo perché lo SKIP LOCKED viene valutato ** dopo ** la clausola WHERE. Usa una selezione senza rownum, recupera una (o più come necessario) riga e chiudi il cursore, questo è il modo migliore per usare SKIP LOCKED. –

+0

infatti, ho dovuto mettere la selezione e l'aggiornamento in una transazione, ora funziona. Grazie!!! –

2

Ogni processo può emettere un SELECT ... FOR UPDATE per bloccare la riga quando viene letta. In questo scenario, il processo A leggerà e bloccherà la riga, il processo B tenterà di leggere la riga e il blocco finché il processo A non rilascerà il blocco commettendo (o ripristinando) la sua transazione. Oracle determinerà quindi se la riga soddisferà ancora i criteri di B e, nel tuo esempio, non restituirà la riga a B. Funziona ma significa che il tuo processo a più thread potrebbe ora essere effettivamente a thread singolo a seconda di come il tuo controllo delle transazioni ha bisogno di lavorare

possibili modi per migliorare la scalabilità

  • Un approccio relativamente comune sul consumatore per risolvere questo è di avere un unico filo coordinatore che legge i dati dalla tabella, i pacchi dei lavori di diversi thread, e aggiorna la tabella in modo appropriato (tra cui sapere come riassegnare un lavoro se il thread che è stato assegnato è morto).
  • Se si utilizza Oracle 11.1 o versione successiva, è possibile utilizzare SKIP LOCKED clause su FOR UPDATE in modo che ogni sessione recuperi la prima riga che soddisfa i propri criteri e non sia bloccata (la clausola esisteva nelle versioni precedenti ma non era documentata in modo da potrebbe non funzionare correttamente).
  • Invece di utilizzare una tabella per ImportJobs, è possibile utilizzare una coda con più utenti. Ciò consentirà a Oracle di distribuire messaggi a ciascun processo senza che sia necessario creare alcun blocco aggiuntivo (le code Oracle stanno facendo tutto dietro le quinte).
1

Utilizzare versioning and optimistic concurrency.

La tabella IMPORTJOBS deve avere una colonna di timestamp contrassegnata come ConcurrencyMode = Fixed nel modello. Ora, quando EF tenta di eseguire un aggiornamento, la colonna timestamp viene incorporata nell'istruzione update: WHERE timestamp = xxxxx.

Per B, il timestamp è stato modificato nel frattempo, pertanto viene sollevata un'eccezione di concorrenza che, in questo caso, viene gestita saltando l'aggiornamento.

Vengo da un server SQL e non conosco l'equivalente Oracle di timestamp (o rowversion), ma l'idea è che si tratta di un campo che si aggiorna automaticamente quando viene effettuato un aggiornamento su un record.

Problemi correlati