Tracciamento, debug e correzione delle discussioni sui blocchi di riga


12

In ritardo, ho dovuto affrontare molte contese per il blocco delle file. La tabella in conflitto sembra essere una tabella particolare.

Questo è generalmente ciò che accade:

  • Lo sviluppatore 1 avvia una transazione dalla schermata del front-end di Oracle Forms
  • Lo sviluppatore 2 avvia un'altra transazione, da una sessione diversa utilizzando la stessa schermata

~ 5 minuti, il front-end sembra non rispondere. Il controllo delle sessioni mostra la contesa del blocco delle righe. La "soluzione" che tutti lanciano è uccidere le sessioni: /

Come sviluppatore di database

  • Cosa si può fare per eliminare le contese di blocco delle righe?
  • Sarebbe possibile scoprire quale riga di una stored procedure sta causando queste contese di blocco delle righe
  • Quale sarebbe la linea guida generale per ridurre / evitare / eliminare tali problemi quali codifica?

Se questa domanda sembra troppo aperta / informazioni insufficienti, non esitare a modificare / fammi sapere - Farò del mio meglio per aggiungere alcune informazioni aggiuntive.


La tabella in questione è sotto molti inserimenti e aggiornamenti, direi che è una delle tabelle più occupate. L'SP è piuttosto complesso - per semplificare - recupera i dati da varie tabelle, li popola in tabelle di lavoro, molte operazioni aritmetiche si verificano sulla tabella di lavoro e il risultato della tabella di lavoro viene inserito / aggiornato nella tabella in questione.


La versione del database è Oracle Database 10g Enterprise Edition versione 10.2.0.1.0 - 64 bit. Il flusso della logica viene eseguito nello stesso ordine in entrambe le sessioni, la transazione non viene mantenuta aperta per troppo tempo (o almeno credo di sì) e i blocchi si verificano durante l'esecuzione attiva delle transazioni.


Aggiornamento: il conteggio delle righe della tabella è maggiore di quanto mi aspettassi, con circa 3,1 milioni di righe. Inoltre, dopo aver tracciato una sessione, ho scoperto che un paio di istruzioni di aggiornamento a questa tabella non utilizzano l'indice. Perché è così - non ne sono sicuro. La colonna a cui fa riferimento la clausola where è indicizzata. Attualmente sto ricostruendo l'indice.


1
@Sathya: puoi elaborare la complessità della procedura memorizzata? è la tabella sospetta è in aggiornamento rigoroso o inserire?
CoderHawk,

Le chiavi esterne svolgono un ruolo qui? (A volte è necessario un indice) Quale versione del database è disponibile? Il flusso della logica viene eseguito nello stesso ordine in entrambe le sessioni? La transazione è mantenuta "aperta" per molto tempo? Il blocco si verifica durante il tempo di riflessione degli utenti o durante l'esecuzione attiva della transazione?
ik_zelf,

@Sandy Ho aggiornato la domanda
Sathyajith Bhat il

@ik_zelf Ho aggiornato la domanda
Sathyajith Bhat il

1
Non mi è chiaro perché questo sia un problema: Oracle sta facendo esattamente quello che dovrebbe fare, ovvero serializzare l'accesso a una singola riga. Se qualcuno ha quella riga, puoi leggere la versione precedente di essa, ma per scrivere devi aspettare che rilasci il blocco. L'unica "soluzione" è che a) non scherzare e / COMMITo ROLLBACKin un tempo ragionevole oppure b) organizzare in modo tale che le stesse persone non vogliano sempre la stessa fila allo stesso tempo.
Gaius,

Risposte:


10

Sarebbe possibile scoprire quale riga di una procedura memorizzata sta causando queste contese di blocco delle righe?

Non esattamente, ma è possibile ottenere l'istruzione SQL che causa il blocco e, a sua volta, identificare le righe correlate nella procedura.

SELECT sid, sql_text
FROM v$session s
LEFT JOIN v$sql q ON q.sql_id=s.sql_id
WHERE state = 'WAITING' AND wait_class != 'Idle'
AND event = 'enq: TX - row lock contention';

Quale sarebbe la linea guida generale per ridurre / evitare / eliminare tali problemi con la codifica?

La sezione Guida ai concetti di Oracle sui blocchi dice "Una riga è bloccata solo se modificata da uno scrittore". Un'altra sessione che aggiorna la stessa riga attenderà quindi la prima sessione COMMITo ROLLBACKprima che possa continuare. Per eliminare il problema potresti serializzare gli utenti, ma qui ci sono alcune cose che possono ridurre il problema forse al livello di non essere un problema.

  • COMMITpiù frequentemente. Ogni versione viene COMMITbloccata, quindi se è possibile eseguire gli aggiornamenti in batch, viene ridotta la probabilità che un'altra sessione richieda la stessa riga.
  • Assicurati di non aggiornare alcuna riga senza modificarne i valori. Ad esempio, UPDATE t1 SET f1=DECODE(f2,’a’,f1+1,f1);dovrebbe essere riscritto come più selettivo (leggi meno blocchi) UPDATE t1 SET f1=f1+1 WHERE f2=’a’;. Naturalmente se la modifica dell'istruzione bloccherà comunque la maggior parte delle righe nella tabella, la modifica avrà solo un vantaggio di leggibilità.
  • Assicurati di utilizzare le sequenze anziché bloccare una tabella per aggiungerne una al valore corrente più alto.
  • Assicurati di non utilizzare una funzione che impedisce l'utilizzo di un indice. Se la funzione è necessaria, considerare di renderla un indice basato sulla funzione.
  • Pensa in set. Valuta se un ciclo che esegue un blocco di PL / SQL che esegue gli aggiornamenti possa essere riscritto come una singola istruzione di aggiornamento. In caso contrario, forse potrebbe essere utilizzata l'elaborazione in blocco BULK COLLECT ... FORALL.
  • Ridurre il lavoro svolto tra il primo UPDATEe il COMMIT. Ad esempio, se il codice invia un'e-mail dopo ogni aggiornamento, considerare di mettere in coda le e-mail e di inviarle dopo aver eseguito il commit degli aggiornamenti.
  • Progettare l'applicazione per gestire l'attesa facendo un SELECT ... FOR UPDATE NOWAITo WAIT 2. È quindi possibile rilevare l'impossibilità di bloccare la riga e informare l'utente che un'altra sessione sta modificando gli stessi dati.

7

Fornirò una risposta dal punto di vista degli sviluppatori.

Secondo me, quando incontri una disputa di riga come quella che descrivi, è perché hai un bug nella tua applicazione. Nella maggior parte dei casi questo tipo di contesa è indice di una vulnerabilità di aggiornamento perduto. Questo thread su AskTom spiega il concetto di un aggiornamento perso:

Un aggiornamento perso si verifica quando:

sessione 1: leggere il registro dei dipendenti di Tom

sessione 2: leggere il registro dei dipendenti di Tom

sessione 1: aggiorna il record dei dipendenti di Tom

sessione 2: aggiorna il record dei dipendenti di Tom

La sessione 2 sovrascriverà le modifiche della sessione 1 senza mai vederle, causando un aggiornamento perso.

Si è verificato un brutto effetto collaterale dell'aggiornamento perso: la sessione 2 può essere bloccata perché la sessione 1 non è stata ancora impegnata. Il problema principale tuttavia è che la sessione 2 aggiorna ciecamente il record. Supponiamo che entrambe le sessioni emettano la dichiarazione:

UPDATE table SET col1=:col1, ..., coln=:coln WHERE id = :pk

Dopo entrambe le istruzioni, le modifiche alla sessione 1 sono state sovrascritte, senza che la sessione 2 sia stata informata che la riga era stata modificata dalla sessione 1.


L'aggiornamento perso (e l'effetto collaterale della contesa) non dovrebbe mai avvenire, sono evitabili al 100%. È necessario utilizzare il blocco per prevenirli con due metodi principali: blocco ottimistico e pessimistico .

1) Blocco pessimistico

Vuoi aggiornare una riga. In questa modalità impedirai ad altri di modificare questa riga richiedendo un blocco su quella riga ( SELECT ... FOR UPDATE NOWAITistruzione). Se la riga è già in fase di modifica, verrà visualizzato un messaggio di errore che è possibile tradurre con garbo all'utente finale (questa riga viene modificata da un altro utente). Se la riga è disponibile, apporta le modifiche (AGGIORNAMENTO), quindi esegui il commit ogni volta che la transazione è completa.

2) Blocco ottimistico

Vuoi aggiornare una riga. Tuttavia, non si desidera mantenere un blocco su quella riga, forse perché si utilizzano diverse transazioni per aggiornare la riga (applicazione stateless basata sul web) o forse non si desidera che nessun utente mantenga un blocco per troppo tempo ( che può comportare il blocco di altre persone). In tal caso non richiederai subito un blocco. Utilizzerai un marcatore per assicurarti che la riga non sia cambiata quando verrà emesso l'aggiornamento. È possibile memorizzare nella cache il valore di tutte le colonne oppure utilizzare una colonna timestamp che viene aggiornata automaticamente o una colonna basata su sequenza. Qualunque sia la tua scelta, quando stai per eseguire l'aggiornamento, ti assicurerai che l'indicatore su quella riga non sia cambiato emettendo una query come:

SELECT <...>
  FROM table
 WHERE id = :id
   AND marker = :marker
   FOR UPDATE NOWAIT

Se la query restituisce una riga, effettua l'aggiornamento. In caso contrario, ciò significa che qualcuno ha modificato la riga dall'ultima volta che lo hai interrogato. Dovrai riavviare il processo dall'inizio.

Nota: se si dispone di una fiducia completa su tutte le applicazioni che accedono al proprio DB, è possibile fare affidamento su un aggiornamento diretto per il blocco ottimistico. Puoi emettere direttamente:

UPDATE table
   SET <...>, 
       marker = marker + 1
 WHERE id = :id;

Se l'istruzione non aggiorna nessuna riga, sai che qualcuno ha modificato questa riga e devi ricominciare tutto da capo.

Se tutte le applicazioni concordano su questo schema, non verrai mai bloccato da qualcun altro e eviterai l'aggiornamento cieco. Tuttavia, se non si blocca in anticipo la riga, si è comunque soggetti al blocco indefinito se un'altra applicazione, processo batch o aggiornamento diretto non implementa il blocco ottimistico. Questo è il motivo per cui ti consiglio di bloccare sempre la riga, qualunque sia la tua scelta dello schema di blocco (il colpo di prestazione può essere trascurabile poiché recuperi tutti i valori incluso il rowid quando blocchi la riga).

TL; DR

  • L'aggiornamento di una riga senza averne prima bloccato un blocco espone l'applicazione al potenziale "blocco". Questo può essere evitato se tutti i DML nel DB implementano il blocco ottimistico o pessimistico.
  • Verificare che l'istruzione SELECT restituisca valori coerenti con qualsiasi SELECT precedente (per evitare qualsiasi problema di aggiornamento perso)

5

Questa risposta probabilmente si qualificherebbe per una voce in The Daily WTF.

Bene, dopo aver rintracciato le sessioni e aver cercato attraverso USER_SOURCE- ho rintracciato la causa principale

  • La causa, ovviamente, era una logica imperfetta
  • Di recente è stata aggiunta una dichiarazione di aggiornamento a SP. L'istruzione update aggiornerebbe sostanzialmente l'intera tabella. Apparentemente lo sviluppatore in questione ha dimenticato di aggiungere le clausole where corrette per aggiornare le dichiarazioni richieste.
  • La tabella da aggiornare era come menzionata sopra, una delle tabelle più trattate e aveva un gran numero di record. L'aggiornamento richiederebbe un tempo lungo e angosciante.
  • Il risultato è stato che altre sessioni non sono state in grado di ottenere un blocco sul tavolo e si sarebbero sedute in contese di blocco delle righe.
Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.