Проблемы с блокировкой транзакций в Hibernate 4.0

Иногда у меня возникают проблемы с блокировкой, например:

java.sql.SQLTransactionRollbackException: A lock could not be obtained within the time requested

Я использую Hibernate с пулом C3p0, и Hibernate настроен на оптимистическую блокировку.

У меня также есть код, который обходит Hibernate и взаимодействует с базой данных через независимо настроенный пул c3p0. Это просто потому, что этот код существовал до того, как я перешел на Hibernate, и работает отлично, поэтому в то время я не видел необходимости его менять.

Теперь мне интересно, может ли наличие двух независимо настроенных пулов c3p0 вызывать проблемы. Если нет, то как я могу отследить причину этих исключений, у меня настроен пул от 20 до 100 соединений, и у меня одновременно есть максимум двенадцать потоков, и я думаю, что все мои транзакции/сеанс закрываются, когда я заканчиваю с ними.

РЕДАКТИРОВАТЬ: теперь у вас есть один пул, но проблема все еще возникает, получая следующую ошибку, но без подробностей о ее причине, я заметил, что он всегда говорит Управляемый поток: 3

Exception with lookup
12:42:36,627  WARN ThreadPoolAsynchronousRunner:608 - com.mchange.v2.async.ThreadPoolAsynchronousRunner$DeadlockDetector@1ff96a2 -- APPARENT DEADLOCK!!! Creating emergency threads for unassigned pending tasks!
12:42:36,628  WARN ThreadPoolAsynchronousRunner:624 - com.mchange.v2.async.ThreadPoolAsynchronousRunner$DeadlockDetector@1ff96a2 -- APPARENT DEADLOCK!!! Complete Status: 
    Managed Threads: 3
    Active Threads: 3
    Active Tasks: 
        com.mchange.v2.c3p0.stmt.GooGooStatementCache$1StatementCloseTask@fdfb9a (com.mchange.v2.async.ThreadPoolAsynchronousRunner$PoolThread-#0)
        com.mchange.v2.c3p0.stmt.GooGooStatementCache$1StatementCloseTask@914847 (com.mchange.v2.async.ThreadPoolAsynchronousRunner$PoolThread-#2)
        com.mchange.v2.c3p0.stmt.GooGooStatementCache$1StatementCloseTask@205390 (com.mchange.v2.async.ThreadPoolAsynchronousRunner$PoolThread-#1)
    Pending Tasks: 
        com.mchange.v2.c3p0.stmt.GooGooStatementCache$1StatementCloseTask@4e171b
        com.mchange.v2.resourcepool.BasicResourcePool$1RefurbishCheckinResourceTask@ceeecb
        com.mchange.v2.resourcepool.BasicResourcePool$1RefurbishCheckinResourceTask@19f7cec
        com.mchange.v2.resourcepool.BasicResourcePool$1RefurbishCheckinResourceTask@1c299f9
        com.mchange.v2.c3p0.stmt.GooGooStatementCache$1StmtAcquireTask@10ab38a
        com.mchange.v2.c3p0.stmt.GooGooStatementCache$1StmtAcquireTask@1916a2f
        com.mchange.v2.resourcepool.BasicResourcePool$1RefurbishCheckinResourceTask@1d23fbf
        com.mchange.v2.c3p0.stmt.GooGooStatementCache$1StmtAcquireTask@573b7c
        com.mchange.v2.resourcepool.BasicResourcePool$1RefurbishCheckinResourceTask@1027733
        com.mchange.v2.resourcepool.BasicResourcePool$1RefurbishCheckinResourceTask@dfd9b0
        com.mchange.v2.c3p0.stmt.GooGooStatementCache$1StmtAcquireTask@4cecbb
        com.mchange.v2.c3p0.stmt.GooGooStatementCache$1StatementCloseTask@4a0d0b
        com.mchange.v2.resourcepool.BasicResourcePool$1RefurbishCheckinResourceTask@19e809d
        com.mchange.v2.c3p0.stmt.GooGooStatementCache$1StmtAcquireTask@10de0f8
        com.mchange.v2.c3p0.stmt.GooGooStatementCache$1StmtAcquireTask@2ce568
Pool thread stack traces:
    Thread[com.mchange.v2.async.ThreadPoolAsynchronousRunner$PoolThread-#0,5,JAIKOZ Thread Group]
        org.apache.derby.impl.jdbc.EmbedStatement.close(Unknown Source)
        com.mchange.v1.db.sql.StatementUtils.attemptClose(StatementUtils.java:41)
        com.mchange.v2.c3p0.stmt.GooGooStatementCache$1StatementCloseTask.run(GooGooStatementCache.java:404)
        com.mchange.v2.async.ThreadPoolAsynchronousRunner$PoolThread.run(ThreadPoolAsynchronousRunner.java:547)
    Thread[com.mchange.v2.async.ThreadPoolAsynchronousRunner$PoolThread-#2,5,JAIKOZ Thread Group]
        org.apache.derby.impl.jdbc.EmbedStatement.close(Unknown Source)
        com.mchange.v1.db.sql.StatementUtils.attemptClose(StatementUtils.java:41)
        com.mchange.v2.c3p0.stmt.GooGooStatementCache$1StatementCloseTask.run(GooGooStatementCache.java:404)
        com.mchange.v2.async.ThreadPoolAsynchronousRunner$PoolThread.run(ThreadPoolAsynchronousRunner.java:547)
    Thread[com.mchange.v2.async.ThreadPoolAsynchronousRunner$PoolThread-#1,5,JAIKOZ Thread Group]
        org.apache.derby.impl.jdbc.EmbedStatement.close(Unknown Source)
        com.mchange.v1.db.sql.StatementUtils.attemptClose(StatementUtils.java:41)
        com.mchange.v2.c3p0.stmt.GooGooStatementCache$1StatementCloseTask.run(GooGooStatementCache.java:404)
        com.mchange.v2.async.ThreadPoolAsynchronousRunner$PoolThread.run(ThreadPoolAsynchronousRunner.java:547)

Может в этом проблема

https://forum.hibernate.org/viewtopic.php?p=2390809


person Paul Taylor    schedule 16.04.2012    source источник
comment
Я мог бы, вероятно, перейти на h2, если это лучше   -  person Paul Taylor    schedule 18.04.2012
comment
H2 поддерживает параллелизм нескольких версий. Предполагается, что это поможет избежать использования мьютексов и блокировок, как в моем примере ниже. Наверное, не мешало бы попробовать.   -  person Jason Huntley    schedule 18.04.2012


Ответы (2)


Учитывая, что SQLTransactionRollbackException — это сбой блокировки на уровне базы данных, самостоятельно настроенные пулы c3p0 не являются причиной этой проблемы. Если бы это было так, вы бы не смогли запустить два экземпляра одного и того же приложения на основе Hibernate.

Первым шагом здесь должно быть использование отладчика для остановки при возникновении этого исключения. Затем проверьте другие потоки пула соединений с базой данных, чтобы узнать, не делают ли они что-либо с базой данных. Если это так, это будет первым местом для поиска, так как у вас может возникнуть взаимоблокировка, вызванная блокировками на уровне базы данных. Этот шаг может быть проще, если вы можете уменьшить количество потоков в пулах, продолжая воспроизводить проблему.

Возможно, однако, что причиной является блокировка базы данных, полученная другим потоком, а затем не снятая. Если это так, вам придется использовать свои инструменты базы данных, чтобы выяснить, какой ресурс не может быть получен потоком, создающим исключение, а затем попытаться выяснить, кому принадлежит эта блокировка и почему.

Удачи.

person sharakan    schedule 18.04.2012
comment
Что ж, я изменил код, и проблема, похоже, исчезла, но мне пришлось изменить некоторые другие вещи, чтобы сделать это, так что, возможно, вы правы, и это совпадение. Одна вещь, которая меня смущает, это то, что сеансы Hibernate, открытые дольше, заставляют его дольше удерживать блокировки базы данных, раньше я не думал, что это так, но теперь я не уверен. - person Paul Taylor; 18.04.2012
comment
Если вы делаете что-то, что может привести к блокировке, и никогда не говорите сеансу, что вы закончили, то да, эти блокировки все еще будут удерживаться. Иными словами, я бы, конечно, ожидал, что любые блокировки БД, открытые сеансом, будут закрыты в .close() или в .commit(), если внутри транзакции. Если вы никогда не вызовете эти методы, они будут удерживаться в течение некоторого времени в зависимости от настроек базы данных. - person sharakan; 18.04.2012
comment
Я всегда закрываю и фиксирую, но если сеанс открыт в течение некоторого времени, это не очень помогает, я не знаю, как узнать, когда Hibernate действительно получает блокировку. - person Paul Taylor; 18.04.2012
comment
Это действительно вопрос базы данных, а не спящего режима. Это зависит от выполняемого SQL и настроек транзакции, а также от того, на что способна реализация te db: например, блокировка на уровне строк или таблица? Возможно, вы сможете отследить это, пошагово пройдясь по коду и используя специальные инструменты для базы данных, чтобы посмотреть, какие блокировки остаются незавершенными. - person sharakan; 18.04.2012

У меня возникли проблемы с Hibernate во встроенной среде. Мы используем SQLite для выполнения операций с базой данных на стороне клиента. Я обнаружил, что встроенные базы данных имеют ограниченные или грубые методы обработки многопоточных операций. Вам, вероятно, придется проверять блокировку базы данных перед фиксацией или, возможно, даже запуском каких-либо транзакций.

С SQLite вы можете вводить потоки для чтения во время выполнения транзакции. Однако вы не можете иметь один или несколько потоков для одновременной записи или открытия транзакций. Кроме того, настоящий стержень, которого было трудно найти, вы не можете зафиксировать транзакцию, пока выполняется какой-либо запрос! В противном случае вы столкнетесь с исключением блокировки, подобным тому, с которым столкнулись вы.

На самом деле я создал блокировку с фиксацией семафора, чтобы отслеживать все открытые операции чтения и обеспечивать безопасное взаимодействие с транзакциями:

    /**
     * Enter read section. Increment the latch so commiting
     * threads know how many reads are left till it's appropriate
     * to write/commit.
     *
     * @throws InterruptedException the interrupted exception
     */
    public void enterReadSection() throws InterruptedException {
        if (enableReadLock && (transactionLock.availablePermits()==0)) {
            readLock.lock();
            try {
                log.debug("Waiting on database unlock.");
                readWait.await();
            } finally {
                readLock.unlock();
                log.debug("Database Unlocked.");
            }
        }

        if (enableReadLock) {
            synchronized(this) {
                latch = new CountDownLatch((int)latch.getCount()+1);
            }
        }
    }

    /**
     * Exit read section.
     */
    public void exitReadSection( ) {
        if (enableReadLock)
            latch.countDown();
    }

   /**
     * Trx lock.
     *
     * @throws InterruptedException the interrupted exception
     */
    public void trxLock() throws InterruptedException {
        if (enableTrxLock)
            transactionLock.acquire();
    }

    /**
     * Trx unlock.
     */
    public void trxUnlock() {
        if (enableTrxLock)
            transactionLock.release();
    }

    /**
     * Commit lock.
     *
     * @throws InterruptedException the interrupted exception
     */
    public void commitLock() throws InterruptedException {
        if (enableCommitLock) {
            commitLock.acquire();

            //Wait for reading threads to complete
            latch.await();
        }
    }

    /**
     * Commit unlock.
     *
     * @throws InterruptedException the interrupted exception
     */
    public void commitUnlock() throws InterruptedException {
        if(enableCommitLock) {
            commitLock.release();
            releaseRead();
        }
    }

Я знаю, что там происходит какая-то глубокая магия, но это то, к чему я пришел после долгих проб и ошибок. Я бы хотел, чтобы это было проще, но обычно клиент-серверные интерфейсы для баз данных снимают большую часть проблем с параллелизмом, поэтому вы не видите этого с Oracle, mysql, postgres и т. д.

Я предполагаю, что вам понадобится какой-то механизм проверки параллелизма, семафор или защелка при работе с дерби. Я не так много знаю о дерби, но похоже, что в него встроены примитивные механизмы безопасности потоков. Вероятно, вам придется обойти эти ограничения. Удачи!

person Jason Huntley    schedule 18.04.2012