web-dev-qa-db-ja.com

hibernateロック待機タイムアウトを超えました。

私はHibernateを使用しており、データベースの同じ行に対する2つの同時更新をシミュレートしようとしています。

編集:em1.getTransaction()。commitをem1.flush();の直後に移動しました。 2つのトランザクションが正常にコミットされたStaleObjectExceptionが発生しません。

_Session em1=Manager.sessionFactory.openSession();
Session em2=Manager.sessionFactory.openSession();

em1.getTransaction().begin();
em2.getTransaction().begin();

UserAccount c1 = (UserAccount)em1.get( UserAccount.class, "root" );
UserAccount c2 = (UserAccount)em2.get( UserAccount.class, "root" );

c1.setBalance( c1.getBalance() -1 );
em1.flush();
System.out.println("balance1 is "+c2.getBalance());
c2.setBalance( c2.getBalance() -1 );
em2.flush(); // fail

em1.getTransaction().commit();
em2.getTransaction().commit();

System.out.println("balance2 is "+c2.getBalance());
_

em2.flush()で次の例外が発生します。どうして?

_2009-12-23 21:48:37,648  WARN JDBCExceptionReporter:100 - SQL Error: 1205, SQLState: 41000
2009-12-23 21:48:37,649 ERROR JDBCExceptionReporter:101 - Lock wait timeout exceeded; try restarting transaction
2009-12-23 21:48:37,650 ERROR AbstractFlushingEventListener:324 - Could not synchronize database state with session
org.hibernate.exception.GenericJDBCException: Could not execute JDBC batch update
    at org.hibernate.exception.SQLStateConverter.handledNonSpecificException(SQLStateConverter.Java:126)
    at org.hibernate.exception.SQLStateConverter.convert(SQLStateConverter.Java:114)
    at org.hibernate.exception.JDBCExceptionHelper.convert(JDBCExceptionHelper.Java:66)
    at org.hibernate.jdbc.AbstractBatcher.executeBatch(AbstractBatcher.Java:275)
    at org.hibernate.persister.entity.AbstractEntityPersister.processGeneratedProperties(AbstractEntityPersister.Java:3702)
    at org.hibernate.persister.entity.AbstractEntityPersister.processUpdateGeneratedProperties(AbstractEntityPersister.Java:3691)
    at org.hibernate.action.EntityUpdateAction.execute(EntityUpdateAction.Java:147)
    at org.hibernate.engine.ActionQueue.execute(ActionQueue.Java:279)
    at org.hibernate.engine.ActionQueue.executeActions(ActionQueue.Java:263)
    at org.hibernate.engine.ActionQueue.executeActions(ActionQueue.Java:168)
    at org.hibernate.event.def.AbstractFlushingEventListener.performExecutions(AbstractFlushingEventListener.Java:321)
    at org.hibernate.event.def.DefaultFlushEventListener.onFlush(DefaultFlushEventListener.Java:50)
    at org.hibernate.impl.SessionImpl.flush(SessionImpl.Java:1028)
    at com.ch.whoisserver.test.StressTest.main(StressTest.Java:54)
Caused by: Java.sql.BatchUpdateException: Lock wait timeout exceeded; try restarting transaction
    at com.mysql.jdbc.PreparedStatement.executeBatchSerially(PreparedStatement.Java:1213)
    at com.mysql.jdbc.PreparedStatement.executeBatch(PreparedStatement.Java:912)
    at org.hibernate.jdbc.BatchingBatcher.doExecuteBatch(BatchingBatcher.Java:70)
    at org.hibernate.jdbc.AbstractBatcher.executeBatch(AbstractBatcher.Java:268)
    ... 10 more
_
10
user217631

さて、あなたはデッドロックに陥ろうとしていて、成功しています:-)

  1. Transaction1が開始し、エンティティで行を更新(およびロック)します。
  2. Transaction2は同じことを試みますが、行がまだロックされているためできません。したがって、タイムアウトを超えるまで待機します(そして待機して待機します)

実際のシミュレーションでは、1番目と2番目のエンティティマネージャーに加えて、個別のスレッドで適切な更新/トランザクションが行われます。そうすれば、次のようになります。

  1. Transaction1が開始し、エンティティで行を更新(およびロック)します。
  2. Transaction2は同じことを試みますが、行がまだロックされているためできません。だからそれは待つ(そして待つ、そして待つ).。
  3. その間、Transaction1はコミットされ、ロックが解放されます
  4. Transaction2を続行できるようになりました

その時点(上記の#4)で、Transaction1によって行われた変更を上書きすることに注意してください。 Hibernateは、 楽観的ロック および 悲観的ロック を使用して、それが発生しないようにすることができます。

更新(コメントに基づく):

エンティティがバージョン管理されている場合、Transaction2(上記の#4)は失敗します。ただし、Transaction2は上記で説明したようにロックを取得できないため、投稿されたコードはその時点に到達しません。楽観的なバージョン管理が機能していることを具体的にテストする場合は、次の操作を実行できます。

  1. Em1を取得し、トランザクションを開始し、エンティティを取得し、commitトランザクション、close em1。
  2. Em2を取得し、トランザクションを開始し、エンティティを取得し、エンティティを更新し、トランザクションをコミットし、em2を閉じます。
  3. Em3を取得し、トランザクションを開始し、ステップ1でロードしたエンティティの更新を試みます-テストはここで失敗するはずです。
22
ChssPly76