2009-11-24 10 views
9

Sto utilizzando Hibernate EntityManager e sto vivendo un bizzarro rallentamento nelle mie query su Hibernate. Date un'occhiata a questo codice:Le query di ibernazione rallentano drasticamente dopo che un'entità viene caricata nella sessione

public void testQuerySpeed() { 
    for(int i = 0; i < 1000; i++) { 
     em.createNativeQuery("SELECT 1").getResultList(); 
    } 
} 

Questo funziona in circa 750ms sulla mia macchina. Non incredibilmente veloce considerando che si tratta solo di selezionare un numero intero costante, ma accettabile. Il mio problema si pone nel momento in qualsiasi entità vengono caricati nella mia sessione EntityManager prima di lanciare la mia domanda:

public void testQuerySpeed() { 
    CommercialContact contact = em.find(CommercialContact.class, 1890871l); 

    for(int i = 0; i < 1000; i++) { 
     em.createNativeQuery("SELECT 1").getSingleResult(); 
    } 
} 

L'em.find() è veloce, ma le query runtime 1000 è aumentato di oltre dieci volte, a circa 10 secondi. Se inserisco uno em.clear() dopo lo em.find(), il problema scompare nuovamente e il runtime torna a 750 ms.

Qui ho usato una query nativa, ma il problema esiste anche con le query HQL. Sembra che TUTTE le query richiedano almeno 70 ms ogni volta che un'entità si trova nella sessione EntityManager.

Questo calo delle prestazioni ci fa davvero male quando si generano elenchi in cui sono necessarie n + 1 query.

Ho testato l'ultima versione di Hibernate 3.5 beta e ho lo stesso identico problema. Qualcuno ha visto questo problema o qualche idea su come risolverlo?

Sto utilizzando PostgreSQL 8.3, utilizzando le transazioni locali delle risorse (in esecuzione in Tomcat). Utilizzando il pool di connessione integrato, ma l'utilizzo di C3P0 non ha fatto alcuna differenza.

+0

hai il profilo it? Dove si trascorre la maggior parte del tempo? Avete definito metodi di ascolto/intercettazione/callback? – ChssPly76

risposta

10

Devo anche raccomandare l'uso di un profiler JVM per vedere dove sta andando il tempo. Potrebbe anche non essere dannoso attivare la registrazione dell'istruzione SQL per la sessione di ibernazione solo per assicurarsi che non si stia eseguendo più SQL di quanto si pensi.

La cosa che prima viene in mente qui è il comportamento "flushing" della Hibernate Session. Impostate esplicitamente una modalità flush specifica sulla Session? In caso contrario, si otterrà lo svuotamento "automatico" che eseguirà una certa quantità di controllo degli oggetti presenti nella sessione per determinare se ci sono o meno cambiamenti nella memoria che devono essere "svuotati" al database (all'interno di una transazione , ovviamente).

Credo che la cosa più facile da provare prima per vedere se ha qualche effetto è quello di modificare il codice di prova che avete mostrato qui sopra per specificare che si desidera vampate di calore si verificano manualmente quando si commettono la transazione del database:

public void testQuerySpeed() { 
    em.setFlushMode(FlushModeType.COMMIT); // assuming you're using JPA annotations 
    CommercialContact contact = em.find(CommercialContact.class, 1890871l); 

    for(int i = 0; i < 1000; i++) { 
     em.createNativeQuery("SELECT 1").getSingleResult(); 
    } 
} 

Un altro pensiero suppongo sarebbe quello di chiedere se è possibile eseguire le attività di massa in un EntityManager separato, che potrebbe funzionare se si sta facendo solo UPDATE o INSERT.

+1

Infatti, la modalità flush era impostata su AUTO nel mio codice. Ho impostato la modalità a filo su COMMIT nel nostro codice di segnalazione, in ogni caso non eseguiamo alcuna creazione di entità. Questo risolve efficacemente il problema delle prestazioni. È strano che il controllo degli oggetti sporchi abbia richiesto circa 60ms per una sola entità. Grazie mille per la tua opinione! –

+0

OMG, grazie mille, Alexander e BryandD! Entrambi mi hai salvato la vita! :) Ho avuto esattamente lo stesso problema qui, con una transazione contenente un ciclo con diverse ricerche e inserimenti/aggiornamenti collettivi all'interno. Dopo una settimana di modifica degli algoritmi, google e profilazione, il mio problema di prestazioni è stato risolto con un semplice: em.setFlushMode (FlushModeType.COMMIT); Prima di questo, l'intero processo richiedeva circa 28 minuti, il 95% speso per getResultList(). Dopo, tutto va bene con solo circa 2 minuti. Mi chiedo perché jpa/hibernate non scelga FlushModeType.COMMIT il default FlushMode per EntityManager ... – hbobenicio

1

Esegui una query nativa che non sta dicendo nulla di ciò che sarà toccante e quindi Hibernate dovrà (per coerenza) flush() contro tutti i dati di tutte le tabelle di cui è a conoscenza (e il tuo singolo find() potrebbe aver recuperato più di un solo oggetto, quindi potrebbe non essere un'operazione banale).

Per ottimizzare ciò, assicurarsi di utilizzare i metodi SQLQuery.add * per definire cosa effettivamente sta facendo la query. In questo caso query.addSynchronizedQuerySpace ("bogustablename") dovrebbe fare il trucco per dire a Hibernate che questa query è solo dati scalari da nessuna tabella specifica.

3

Avevo essenzialmente lo stesso problema (query all'interno di un ciclo). Ho proceduto a una verifica del profilo con JProfiler ...l'esecuzione del mio metodo interessato ha impiegato 572 secondi e il controllo sporco dell'ibernazione richiede 457 secondi (circa l'80%). Incredibile, non è vero? Devo dire che ho avuto molte entità gestite da EntityManager. Se introduco em.flush()/em.clear() o em.setFlushMode (FlushModeType.COMMIT) nel codice incriminato, il problema delle prestazioni scompare.

risultato profiler è disponibile presso http://img1.imagilive.com/0110/hibernate_dirty_checking_bad_perfomances0ce.png

Problemi correlati