StaleObjectStateException на высокочастотных обновлениях - PullRequest
3 голосов
/ 29 апреля 2011

Мы используем Hibernate 3.6.3.Final и MySQL 5.5.8 для веб-приложения.Бэкэнд работает на сервере Final JBoss 6.0.0.В большинстве случаев все работает очень хорошо, но иногда мы получаем исключение StaleObjectStateException.После некоторого времени эксперимента мы выяснили, что его можно воспроизвести, посылая запросы на сервер с высокой частотой (т. Е. Нажимая кнопку, которая запускает запрос как можно быстрее).

Насколько я знаюисключение означает, что объект домена был получен из базы данных, и когда Hibernate попытался снова его сохранить, он заметил, что другая транзакция за это время изменила его.

Однако, насколько я понимаю базы данных, конфликтующие транзакции должны быть изолированыдо такой степени, которая предотвращает именно это поведение.Я явно изменил уровень изоляции на SERIALIZABLE, который гарантирует повторяемое чтение, и отключил кэширование Hibernate.Это должно предотвратить ситуацию, когда одна транзакция видит разные версии одного и того же доменного объекта.

Полная трассировка стека:

    2011-04-28 20:46:17,865 WARN  [com.arjuna.ats.arjuna] (WorkerThread#2[127.0.0.1:57772]) ARJUNA-12125 TwoPhaseCoordinator.beforeCompletion - failed for SynchronizationImple< 0:ffff7f000001:126a:4db9c7b0:74d, org.hibernate.transaction.synchronization.HibernateSynchronizationImpl@481efbaf >: javax.persistence.OptimisticLockException: org.hibernate.StaleObjectStateException: Row was updated or deleted by another transaction (or unsaved-value mapping was incorrect): [xxx.modules.domain.entity.User#118]
        at org.hibernate.ejb.AbstractEntityManagerImpl.wrapStaleStateException(AbstractEntityManagerImpl.java:1243) [:3.6.0.Final]
        at org.hibernate.ejb.AbstractEntityManagerImpl.convert(AbstractEntityManagerImpl.java:1166) [:3.6.0.Final]
        at org.hibernate.ejb.AbstractEntityManagerImpl.convert(AbstractEntityManagerImpl.java:1147) [:3.6.0.Final]
        at org.hibernate.ejb.AbstractEntityManagerImpl.convert(AbstractEntityManagerImpl.java:1153) [:3.6.0.Final]
        at org.hibernate.ejb.AbstractEntityManagerImpl$3.mapManagedFlushFailure(AbstractEntityManagerImpl.java:1067) [:3.6.0.Final]
        at org.hibernate.transaction.synchronization.CallbackCoordinator.beforeCompletion(CallbackCoordinator.java:122) [:3.6.0.Final]
        at org.hibernate.transaction.synchronization.HibernateSynchronizationImpl.beforeCompletion(HibernateSynchronizationImpl.java:51) [:3.6.0.Final]
        at com.arjuna.ats.internal.jta.resources.arjunacore.SynchronizationImple.beforeCompletion(SynchronizationImple.java:97) [:6.0.0.Final]
        at com.arjuna.ats.arjuna.coordinator.TwoPhaseCoordinator.beforeCompletion(TwoPhaseCoordinator.java:274) [:6.0.0.Final]
        at com.arjuna.ats.arjuna.coordinator.TwoPhaseCoordinator.end(TwoPhaseCoordinator.java:94) [:6.0.0.Final]
        at com.arjuna.ats.arjuna.AtomicAction.commit(AtomicAction.java:159) [:6.0.0.Final]
        at com.arjuna.ats.internal.jta.transaction.arjunacore.TransactionImple.commitAndDisassociate(TransactionImple.java:1158) [:6.0.0.Final]
        at com.arjuna.ats.internal.jta.transaction.arjunacore.BaseTransaction.commit(BaseTransaction.java:119) [:6.0.0.Final]
        at com.arjuna.ats.jbossatx.BaseTransactionManagerDelegate.commit(BaseTransactionManagerDelegate.java:75) [:6.0.0.Final]
        at org.jboss.ejb3.tx2.impl.CMTTxInterceptor.endTransaction(CMTTxInterceptor.java:82) [:0.0.1]
        at org.jboss.ejb3.tx2.impl.CMTTxInterceptor.invokeInOurTx(CMTTxInterceptor.java:255) [:0.0.1]
        at org.jboss.ejb3.tx2.impl.CMTTxInterceptor.required(CMTTxInterceptor.java:349) [:0.0.1]
        at org.jboss.ejb3.tx2.impl.CMTTxInterceptor.invoke(CMTTxInterceptor.java:209) [:0.0.1]
        at org.jboss.ejb3.tx2.aop.CMTTxInterceptorWrapper.invoke(CMTTxInterceptorWrapper.java:52) [:0.0.1]
        at org.jboss.aop.joinpoint.MethodInvocation.invokeNext(MethodInvocation.java:102) [jboss-aop.jar:2.2.1.GA]
        at org.jboss.aspects.tx.TxPropagationInterceptor.invoke(TxPropagationInterceptor.java:76) [:1.0.0.GA]
        at org.jboss.aop.joinpoint.MethodInvocation.invokeNext(MethodInvocation.java:102) [jboss-aop.jar:2.2.1.GA]
        at org.jboss.ejb3.tx.NullInterceptor.invoke(NullInterceptor.java:42) [:1.0.3]
        at org.jboss.aop.joinpoint.MethodInvocation.invokeNext(MethodInvocation.java:102) [jboss-aop.jar:2.2.1.GA]
        at org.jboss.ejb3.security.Ejb3AuthenticationInterceptorv2.invoke(Ejb3AuthenticationInterceptorv2.java:182) [:1.7.17]
        at org.jboss.aop.joinpoint.MethodInvocation.invokeNext(MethodInvocation.java:102) [jboss-aop.jar:2.2.1.GA]
        at org.jboss.ejb3.ENCPropagationInterceptor.invoke(ENCPropagationInterceptor.java:41) [:1.7.17]
        at org.jboss.aop.joinpoint.MethodInvocation.invokeNext(MethodInvocation.java:102) [jboss-aop.jar:2.2.1.GA]
        at org.jboss.ejb3.BlockContainerShutdownInterceptor.invoke(BlockContainerShutdownInterceptor.java:67) [:1.7.17]
        at org.jboss.aop.joinpoint.MethodInvocation.invokeNext(MethodInvocation.java:102) [jboss-aop.jar:2.2.1.GA]
        at org.jboss.ejb3.core.context.CurrentInvocationContextInterceptor.invoke(CurrentInvocationContextInterceptor.java:47) [:1.7.17]
        at org.jboss.aop.joinpoint.MethodInvocation.invokeNext(MethodInvocation.java:102) [jboss-aop.jar:2.2.1.GA]
        at org.jboss.aspects.currentinvocation.CurrentInvocationInterceptor.invoke(CurrentInvocationInterceptor.java:67) [:1.0.1]
        at org.jboss.aop.joinpoint.MethodInvocation.invokeNext(MethodInvocation.java:102) [jboss-aop.jar:2.2.1.GA]
        at org.jboss.ejb3.interceptor.EJB3TCCLInterceptor.invoke(EJB3TCCLInterceptor.java:86) [:1.7.17]
        at org.jboss.aop.joinpoint.MethodInvocation.invokeNext(MethodInvocation.java:102) [jboss-aop.jar:2.2.1.GA]
        at org.jboss.ejb3.stateless.StatelessContainer.dynamicInvoke(StatelessContainer.java:392) [:1.7.17]
        at org.jboss.ejb3.session.InvokableContextClassProxyHack._dynamicInvoke(InvokableContextClassProxyHack.java:53) [:1.7.17]
        at org.jboss.aop.Dispatcher.invoke(Dispatcher.java:91) [jboss-aop.jar:2.2.1.GA]
        at org.jboss.aspects.remoting.AOPRemotingInvocationHandler.invoke(AOPRemotingInvocationHandler.java:82) [:1.0.1.GA]
        at org.jboss.remoting.ServerInvoker.invoke(ServerInvoker.java:898) [:6.0.0.Final]
        at org.jboss.remoting.transport.socket.ServerThread.completeInvocation(ServerThread.java:791) [:6.0.0.Final]
        at org.jboss.remoting.transport.socket.ServerThread.processInvocation(ServerThread.java:744) [:6.0.0.Final]
        at org.jboss.remoting.transport.socket.ServerThread.dorun(ServerThread.java:586) [:6.0.0.Final]
        at org.jboss.remoting.transport.socket.ServerThread.run(ServerThread.java:234) [:6.0.0.Final]
Caused by: org.hibernate.StaleObjectStateException: Row was updated or deleted by another transaction (or unsaved-value mapping was incorrect): [xxx.modules.domain.entity.User#118]
        at org.hibernate.persister.entity.AbstractEntityPersister.check(AbstractEntityPersister.java:1932) [:3.6.0.Final]
        at org.hibernate.persister.entity.AbstractEntityPersister.update(AbstractEntityPersister.java:2576) [:3.6.0.Final]
        at org.hibernate.persister.entity.AbstractEntityPersister.updateOrInsert(AbstractEntityPersister.java:2476) [:3.6.0.Final]
        at org.hibernate.persister.entity.AbstractEntityPersister.update(AbstractEntityPersister.java:2803) [:3.6.0.Final]
        at org.hibernate.action.EntityUpdateAction.execute(EntityUpdateAction.java:113) [:3.6.0.Final]
        at org.hibernate.engine.ActionQueue.execute(ActionQueue.java:273) [:3.6.0.Final]
        at org.hibernate.engine.ActionQueue.executeActions(ActionQueue.java:265) [:3.6.0.Final]
        at org.hibernate.engine.ActionQueue.executeActions(ActionQueue.java:185) [:3.6.0.Final]
        at org.hibernate.event.def.AbstractFlushingEventListener.performExecutions(AbstractFlushingEventListener.java:321) [:3.6.0.Final]
        at org.hibernate.event.def.DefaultFlushEventListener.onFlush(DefaultFlushEventListener.java:51) [:3.6.0.Final]
        at org.hibernate.impl.SessionImpl.flush(SessionImpl.java:1216) [:3.6.0.Final]
        at org.hibernate.impl.SessionImpl.managedFlush(SessionImpl.java:383) [:3.6.0.Final]
        at org.hibernate.transaction.synchronization.CallbackCoordinator.beforeCompletion(CallbackCoordinator.java:117) [:3.6.0.Final]
        ... 39 more

Любая помощь приветствуется!

Спасибозаранее Майкл

Ответы [ 2 ]

3 голосов
/ 29 апреля 2011

Похоже, вы настроили Hibernate на использование оптимистического управления параллелизмом .Это означает, что ваша таблица User имеет поле версии, которое Hibernate увеличивает при каждом обновлении строки.

Скорее всего, ваша транзакция начинается в начале HTTP-запроса и заканчивается в конце HTTP-ответа.Это означает, что процесс редактирования пользователя состоит из двух транзакций: одной транзакции для заполнения веб-формы и одной транзакции для сохранения изменений.

В этом случае изменение уровня изоляции базы данных не приведет к каким-либо изменениям.хорошо.Скорее всего, вы получите худшую производительность и масштабируемость .

Неплохо иметь StaleObjectException с.Он отражает реальный мир - люди действительно время от времени работают над одним и тем же, и могут возникнуть конфликты.Вопрос в том, когда конфликт был обнаружен, как вы решаете его таким образом, который удовлетворяет конечных пользователей?Может ли это быть решено без помощи пользователя?

Возможные стратегии:

  • перезаписать изменения предыдущего пользователя (часто не то, что вы хотите - отсюда необходимостьуправление параллелизмом),

  • показывает сообщение об ошибке с просьбой обновить и снова выполнить свои изменения,

  • автоматически объединяет изменения без перезаписиизменения предыдущего пользователя (иногда возможно)

  • информировать пользователя о устаревании его данных и предложить ему способ ручного объединения его изменений

Все зависит от контекста.

2 голосов
/ 29 апреля 2011

Вы очень , очень уверены, что вы установили TX для сериализации? Потому что это никогда не должно происходить в сериализуемой транзакции.

Если два TX читают и изменяют одну и ту же строку в сериализуемой транзакции, то оракул выдает ORA-08177 .

Пожалуйста, проверьте, что Hibernate фактически устанавливает TX как сериализуемый.

Редактировать

Вы можете сделать то, что предложил Jonas, или вы также можете проверить это в своем приложении, получив базовое соединение и вызвав Connection.getIsolationLevel (). Например

Connection c = session.connection()
int level = c.getIsolationLevel()

Редактировать 2

Хорошо, поскольку вы подтвердили, что уровень изоляции в соединении SERIALILIZABLE, вы можете проверить:

  • Что таблицы используют механизм innoDB.
  • Как предложил Джонас, запустите SELECT @@tx_isolation; из своего кода, пока он находится в транзакции. Он должен вернуть SERIALIZABLE . Это нужно для проверки того, что Соединение действительно распространяет уровень изоляции. Это немного параноидально, но что делать ...
  • Убедитесь, что ваш код открывает только одну транзакцию и выполняет все в этой передаче. Я только что проверил уровень изоляции SERIALIZABLE вручную, и он работает как положено (он блокирует любой TX, пытающийся прочитать ту же строку).
  • Последнее средство: убедитесь, что уровень изоляции SERIALIZABLE работает в вашей установке MySQL.

ПРИМЕЧАНИЕ : Как я упоминал ранее, MySQL блокирует любые запросы, пытающиеся читать из одной и той же строки. Это означает, что если у вас есть несколько «общих таблиц», таких как страна, компания, пользователь и т. Д., Которые многие TX читают одновременно, это может заставить ваше приложение работать почти последовательно, а не параллельно.

...