2009-06-11 18 views
8

Abbiamo un'applicazione di flusso di lavoro Java che utilizza un database Oracle per tracciare i passaggi e le interazioni con altri servizi. Durante un flusso di lavoro vengono eseguiti diversi inserimenti/aggiornamenti/selezioni e occasionalmente la selezione non restituirà i dati aggiornati, anche se il commit di inserimento/aggiornamento è stato eseguito prima del completamento corretto. Dopo che gli errori del flusso di lavoro sono usciti (a causa di dati errati), se torniamo indietro e controlliamo il database tramite un'app di terze parti, i dati nuovi/aggiornati verranno visualizzati. Sembra esserci un ritardo tra quando i nostri commit passano e quando sono visibili. Ciò si verifica in circa il 2% di tutte le esecuzioni del flusso di lavoro e aumenta durante l'utilizzo intensivo del database.Oracle lag tra commit e select

Il nostro team di supporto del database ha suggerito di modificare un ritardo max-commit-propagazione del parametro su 0, in quanto predefinito a 700. Questa sembrava essere una possibile soluzione ma alla fine non ha risolto il nostro problema.

L'applicazione viene eseguita su WebSphere e il database Oracle è configurato come origine dati JDBC. Stiamo usando Oracle 10.1g. L'applicazione è scritta in Java 1.5.

Qualsiasi aiuto sarebbe apprezzato.

edit: esempio di codice

DataSource ds; // spring configured 

String sql = "INSERT INTO " + currentTable + " (" + stepId + ',' + stepEntryId + ", " + stepStepId + ", " + stepActionId + ", " + stepOwner + ", " + stepStartDate + ", " + stepDueDate + ", " + stepFinishDate + ", " + stepStatus + ", " + stepCaller + ") VALUES (?, ?, ?, null, ?, ?, ?, null, ?, null)"; 

Connection conn = ds.getConnection(); 
PreparedStatement stmt = conn.prepareStatement(sql); 
// set values 
stmt.executeUpdate(); 
// close connections 

// later on in the code... 
Connection conn = ds.getConnection(); 
PreparedStatement stmt = null; 
ResultSet rset = null; 

String sql = "SELECT " + stepId + ", " + stepStepId + ", " + stepActionId + ", " + stepOwner + ", " + stepStartDate + ", " + stepDueDate + ", " + stepFinishDate + ", " + stepStatus + ", " + stepCaller + " FROM " + currentTable + " WHERE " + stepEntryId + " = ?"; 
stmt = conn.prepareStatement(sql); 

stmt.setLong(1, entryId); 

rset = stmt.executeQuery(); 
//close connections 
+0

Dalla [Documentazione Oracle] (http://download.oracle.com/docs/cd/B14117_01/server.101/b10755/initparams115.htm), sembra che il parametro 'max_commit_propagation_delay' valga solo per l'impostazione RAC. Ti stai connettendo a un'istanza RAC? –

+0

I dati vengono dati come parte di una transazione? O la lettura? –

risposta

1

sono uso usando un ORM? potrebbe essere selezionato dalla cache e non formare il db dopo la modifica.

+0

Non stiamo usando un ORM, le tabelle e le istruzioni SQL sono davvero semplici e vengono utilizzate solo per memorizzare una piccola quantità di informazioni di tracciamento. – Andrew

+0

inserisci un frammento di codice se possibile ... –

+1

se i suggerimenti di @Steve Broberg non funzionano, potresti provare a utilizzare la stessa connessione –

6

Per impostazione predefinita, il comportamento descritto dovrebbe essere impossibile: le modifiche apportate in una transazione impegnata diventano immediatamente disponibili per tutte le sessioni. Esistono tuttavia delle eccezioni:

  1. Si sta utilizzando una delle opzioni WRITE nel comando COMMIT? Se non lo sei, conferma il valore del tuo parametro di inizializzazione COMMIT_WRITE. Se entrambi utilizzano "WRITE BATCH" o in particolare "WRITE BATCH NOWAIT", potresti aprirti a problemi di concorrenza. "WRITE BATCH NOWAIT" viene in genere utilizzato nei casi in cui la velocità delle transazioni di scrittura è di maggiore importanza rispetto ai possibili problemi di concorrenza. Se il parametro di inizializzazione sta usando le varianti "scrivere", è possibile escludere che su una base di transazione specificando la clausola immediato nel commit (see COMMIT)

  2. è la transazione che sta tentando di leggere i dati invocando SET TRANSACTION prima all'altra transazione impegnata? Utilizzando SET TRANSACTION per specificare LIVELLO SERIALIZZAZIONE SOLA LETTURA o SERIALIZABLE comporterà la transazione non vedendo i cambiamenti che si verificano da altre sessioni impegnate che si sono verificati dopo l'invocazione di SET TRANSACTION (see SET TRANSACTION)

edit: vedo che stai usando una classe DataSource. Non ho familiarità con questa classe - presumo che sia una risorsa di condivisione delle connessioni. Mi rendo conto che il tuo attuale design dell'app potrebbe non rendere facile l'uso dello stesso oggetto di connessione nel flusso di lavoro (i passaggi potrebbero essere progettati per funzionare in modo indipendente e non hai costruito una struttura per passare un oggetto di connessione da un passaggio all'altro successivo), ma è necessario verificare che gli oggetti di connessione restituiti all'oggetto DataSource siano "puliti", in particolare per quanto riguarda le transazioni aperte. Potrebbe essere possibile che tu non stia invocando SET TRANSACTION nel tuo codice, ma un altro utente di DataSource potrebbe farlo altrove e restituire la connessione all'origine dati con la sessione ancora in modalità SERIALIZABLE o READ ONLY. Quando si esegue la condivisione delle connessioni, è fondamentale che tutte le connessioni vengano ripristinate prima di consegnarle a un nuovo utente.

Se non si ha controllo o visibilità sul comportamento della classe DataSource, è possibile provare a eseguire un ROLLBACK sulla connessione appena acquisita per assicurarsi che non sia già stata stabilita una transazione permanente.

+0

+1 !!! Non avevo nemmeno considerato che COMMIT potesse essere diverso da ATTESA IMMEDIATA o che il livello di isolamento della transazione fosse diverso da READ COMMITTED. – spencer7593

+0

Scusate per il ritardo nel rimettervi in ​​contatto. Non penso di usare alcuna opzione di scrittura per il commit. Sto solo usando qualunque sia la classe di connessione java predefinita, lo stesso vale per l'impostazione dell'isolamento della transazione. Ecco alcuni collegamenti alla documentazione per le classi java che sto utilizzando: http://java.sun.com/j2se/1.5.0/docs/api/javax/sql/DataSource.html http: //java.sun .com/j2se/1.5.0/docs/api/java/sql/Connection.html http://java.sun.com/j2se/1.5.0/docs/guide/jdbc/getstart/connection.html – Andrew

4

Se il team DBA ha tentato di modificare il parametro max_commit_propagation_delay, probabilmente significa che ci si sta connettendo a un'istanza RAC (i-e: diversi server distinti che accedono a un singolo database).

In tal caso, quando si sta chiudendo e si riapre la connessione nel codice java, è possibile che vi venga risposto da un server diverso. Il parametro delay indica che c'è un piccolo intervallo di tempo quando le due istanze non si troveranno esattamente allo stesso punto nel tempo. La risposta che ottieni è coerente con un punto nel tempo ma potrebbe non essere la più attuale.

Come proposto da KM, la soluzione più semplice sarebbe mantenere la connessione aperta dopo il commit.

In alternativa, è possibile aggiungere un ritardo dopo aver chiuso la connessione se è pratico (se questo è un lavoro batch e il tempo di risposta non è critico per esempio).

0

Sembra un problema con RAC, con connessioni a due diverse istanze e SCN non sincronizzato.

Per ovviare al problema, considerare di non chiudere la connessione al database e recuperarne una nuova, ma riutilizzare la stessa connessione.

Se ciò non è possibile, aggiungere un nuovo tentativo alla query che tenta di recuperare la riga inserita. Se la riga non viene restituita, quindi dormire un po 'e riprovare la query. Metti questo in un ciclo, dopo un numero specificato di tentativi, quindi puoi fallire.

[ADDENDUM]

Nella sua risposta, Steve Broberg (1!) Solleva idee interessanti. Non avevo considerato:

  • il COMMIT potrebbe essere qualcosa di diverso da IMMEDIATE WAIT
  • il livello di isolamento delle transazioni potrebbe essere qualcosa di diverso da READ COMMITTED

ho fatto prendere in considerazione la possibilità di interrogazione flashback, e lo ha liquidato senza accennato, poiché non vi è alcuna ragione apparente per cui l'OP utilizzerebbe la query di flashback e nessuna prova di ciò nel frammento di codice.

[/ ADDENDUM]

0

Una possibile soluzione potrebbe essere quella di utilizzare la transazione JTA. Mantiene la connessione aperta "dietro la scena" su più connessioni jdbc aperte/chiuse. Forse manterrà la tua connessione sullo stesso server ed eviterà questo problema di sincronizzazione.

UserTransaction transaction = (UserTransaction)new InitialContext().lookup("java:comp/UserTransaction"); 
transaction.begin(); 
// doing multiple open/close cxs 
transaction.commit(); 
0

Lo snippet di codice non include effettivamente il commit.

Se si assume/si basa sulla connessione chiusa eseguendo il commit, potrebbe non essere sincrono (ovvero il java potrebbe segnalare la connessione come chiusa quando dice a Oracle di chiudere la connessione, il che significa che potrebbe essere prima del il commit è completato da Oracle).

+0

http : //java.sun.com/j2se/1.5.0/docs/api/java/sql/Connection.html Per impostazione predefinita, un oggetto Connection è in modalità di commit automatico, il che significa che impegna automaticamente le modifiche dopo l'esecuzione di ciascuna dichiarazione. – Andrew

+0

Yuck. Ma lo stesso può valere, dal momento che non hai il controllo su come/quando il commit è stato eseguito. Oltre a ciò, c'è una penalizzazione delle prestazioni (e probabilmente anche l'integrità dei dati) nel commettere inutilmente. Disattivalo, commetti esplicitamente e vedi se il problema scompare. –

0

Non vedo alcun commit nel codice.Sono le affermazioni più importanti in una app di questo tipo, quindi vorrei averli scritti esplicitamente ogni volta, non basandomi su close() o così via.

È anche possibile che l'autocommit sia impostato su true per impostazione predefinita sulla/e connessione/e che spiegherebbe esattamente il comportamento (si commette dopo ogni inserimento/aggiornamento).

È possibile verificare che si impegna esattamente dove si desidera, ad es. alla fine della transazione e non prima?

Se ci sono commit quando si è parzialmente in transito, si ha una condizione di competizione tra i thread che spiegherebbe anche perché ci sono più problemi quando il carico è maggiore.

+0

Ho impostato il commit automatico su true anche se non sono sicuro di come questo spiegherebbe il comportamento che sto vedendo. Eseguo un insert ed executeUpdate() che esegue il commit automatico, quindi eseguo una selezione e quella riga inserita non viene trovata. Non vedo come possa verificarsi una condizione di gara qui, considerando che executeUpdate() non tornerà finché non è stato eseguito il commit. – Andrew

0

"anche se il commit di inserimento/aggiornamento è stato eseguito prima del completamento."

Questo mi suggerisce che si sta eseguendo un commit(), e successivamente ci si aspetta di leggere esattamente gli stessi dati di nuovo (che è la lettura ripetibile).

Questo mi suggerisce che non dovresti commetterlo. Finché si vuole essere sicuri che NO OTHER TASK sia in grado di modificare QUALSIASI dei dati che ESPLICITEVI ASPETTANO di rimanere stabili, non potete permettervi di rilasciare i lock (che è ciò che fa commit).

Si noti che mentre si mantiene un blocco su alcune risorse, gli altri thread si accumuleranno "in attesa che la risorsa diventi disponibile". La probabilità che lo stack sia vuoto al momento del rilascio del blocco aumenta quando il carico generale del sistema aumenta. E ciò che il tuo DBMS concluderà quando tu (finalmente) pubblichi "commit", è concludere che, "hey, wow, questo ragazzo è finalmente finito con questa risorsa, quindi ora posso fare in modo che tutti gli altri ragazzi in attesa provino e facciano la loro cosa con essa (E NON C'E 'NULLA per evitare che "la loro cosa" sia un aggiornamento!) ".

Forse ci sono problemi da fare con l'isolamento dello snapshot di Oracle che sto trascurando. Mi scuso se così.