2

Glassfish を使用した Java アプリケーションでデッドロックの問題が発生してから数日が経ちました - MySQL InnoDB を使用した EJB3

構成: Mysql InnoDB: Ver 14.12 Distrib 5.0.51a、readline 5.2 を使用する debian-linux-gnu (i486) 用

アプリケーション サーバー: Glassfish v2.1

EJB3 での永続化 - JPA - Hibernate

簡単にするために、私が持っている-サービスへのユーザーのサブスクリプション、ログオン、ログオフ、支払い、登録などを処理するサーブレットを備えたSOAシステム...-これらのサービスの毎日の減少を処理するクォーツジョブシステム(cronトリガー)、 「低信用」警告の生成、支払いの検証など...

私の問題:負荷テスト中にどこでもデッドロックが発生しました (100 000 ユーザーのシミュレーション - 30 リクエスト/秒)

返されたスタック サンプル:

Message ID: 
Could not synchronize database state with session org.hibernate.exception.LockAcquisitionException

Complete Message:   
Could not execute JDBC batch update at org.hibernate.exception.SQLStateConverter.convert(SQLStateConverter.java:105) at 
org.hibernate.exception.JDBCExceptionHelper.convert(JDBCExceptionHelper.java:66) at
org.hibernate.jdbc.AbstractBatcher.executeBatch(AbstractBatcher.java:275) at 
org.hibernate.jdbc.AbstractBatcher.prepareStatement(AbstractBatcher.java:114) at 
org.hibernate.jdbc.AbstractBatcher.prepareStatement(AbstractBatcher.java:109) at 
org.hibernate.jdbc.AbstractBatcher.prepareBatchStatement(AbstractBatcher.java:244) at 
org.hibernate.persister.entity.AbstractEntityPersister.update(AbstractEntityPersister.java:2382) at 
org.hibernate.persister.entity.AbstractEntityPersister.updateOrInsert(AbstractEntityPersister.java:2335) at 
org.hibernate.persister.entity.AbstractEntityPersister.update(AbstractEntityPersister.java:2635) at 
org.hibernate.action.EntityUpdateAction.execute(EntityUpdateAction.java:115) 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.DefaultAutoFlushEventListener.onAutoFlush(DefaultAutoFlushEventListener.java:64) at
org.hibernate.impl.SessionImpl.autoFlushIfRequired(SessionImpl.java:996) at 
org.hibernate.impl.SessionImpl.list(SessionImpl.java:1141) at 
org.hibernate.impl.QueryImpl.list(QueryImpl.java:102) at 
org.hibernate.ejb.QueryImpl.getResultList(QueryImpl.java:67) at
net.xxx.server.dao.impl.PaymentDAOImpl.listPaymentsByStateAndCompany(PaymentDAOImpl.java:270)

終わりに注意してください、それは私が行ったコードです: net.xxx.server.dao.impl.PaymentDAOImpl.listPaymentsByStateAndCompany(PaymentDAOImpl.java:270)

この機能:

private static final String QUERY_FOR_PAYMENTS_BY_STATE_AND_COMPANY = " FROM " + Payment.class.getName()
        + " p WHERE p.serviceDefinition.company=:company"
        + " AND p.state = :state";

    @SuppressWarnings("unchecked")
    public List<Payment> listPaymentsByStateAndCompany(Company company,Constants.PaymentState state) {
        List<Payment> payments = this.getEntityManager()
        .createQuery(QUERY_FOR_PAYMENTS_BY_STATE_AND_COMPANY)
        .setParameter("state",state.ordinal())
        .setParameter("company",company)
        .getResultList();
        return payments;
    }

この関数は、負荷テストを行っていない場合、たとえば 5 秒ごとに 1 つのリクエストがある場合に完全に機能します。

負荷テスト中、高頻度 (たとえば 5 秒ごと) で実行されるジョブがあります。

このエラーだけが発生するのではなく、他のジョブの他のエラーも発生します (まだデッドロック)!

MYSQL の場合:

デッドロックの例:

------------------------
LATEST DETECTED DEADLOCK
------------------------
090428 12:21:11
*** (1) TRANSACTION:
TRANSACTION 0 14286818, ACTIVE 0 sec, process no 21872, OS thread id 802850 starting index read
mysql tables in use 1, locked 1
LOCK WAIT 13 lock struct(s), heap size 1024, undo log entries 2
MySQL thread id 298, query id 11843357 localhost 127.0.0.1 root Updating
/*  */ update service set balance=40.0, company_id=2, last_on='2009-04-28 12:19:55', modified_by='server', modified_on='2009-04-28 12:21:11', service_definition_id=3, state=1, subscriber_id=13578, valid_until='2010-02-22 12:13:52' where service_id=693
*** (1) WAITING FOR THIS LOCK TO BE GRANTED:
RECORD LOCKS space id 0 page no 62 n bits 176 index `PRIMARY` of table `xxx/service` trx id 0 14286818 lock_mode X locks rec but not gap waiting
Record lock, heap no 98 PHYSICAL RECORD: n_fields 12; compact format; info bits 0
 0: len 8; hex 80000000000002b5; asc         ;; 1: len 6; hex 000000d9faa0; asc       ;; 2: len 7; hex 0000000cc91e70; asc       p;; 3: len 4; hex 00001c42; asc    B;; 4: len 8; hex 80001245aad4e363; asc    E   c;; 5: len 6; hex 736572766572; asc server;; 6: len 8; hex 80001245aad4e3c9; asc    E    ;; 7: len 1; hex 81; asc  ;; 8: len 8; hex 80001247f200df08; asc    G    ;; 9: len 8; hex 8000000000000002; asc         ;; 10: len 8; hex 8000000000000003; asc         ;; 11: len 8; hex 800000000000350a; asc       5 ;;

*** (2) TRANSACTION:
TRANSACTION 0 14286798, ACTIVE 1 sec, process no 24963, OS thread id 393239 starting index read, thread declared inside InnoDB 500
mysql tables in use 1, locked 1
17 lock struct(s), heap size 1024, undo log entries 16
MySQL thread id 253, query id 11843359 localhost 127.0.0.1 root Updating
/*  */ update payment set credit=1.0, currency='EUR', modified_by='9999900092', modified_on='2009-04-28 12:21:11', payment_definition_id=7, price=1.0, service_definition_id=3, state=0, subscriber_id=13578, transaction_id=11463 where payment_id=15914
*** (2) HOLDS THE LOCK(S):
RECORD LOCKS space id 0 page no 62 n bits 176 index `PRIMARY` of table `xxx/service` trx id 0 14286798 lock mode S locks rec but not gap
Record lock, heap no 47 PHYSICAL RECORD: n_fields 12; compact format; info bits 0
 0: len 8; hex 8000000000000286; asc         ;; 1: len 6; hex 000000d9ffce; asc       ;; 2: len 7; hex 0000000cc90683; asc        ;; 3: len 4; hex 0000f841; asc    A;; 4: len 8; hex 80001245aad4e3b2; asc    E    ;; 5: len 6; hex 736572766572; asc server;; 6: len 8; hex 80001245aad4e3ff; asc    E    ;; 7: len 1; hex 81; asc  ;; 8: len 8; hex 80001245d450fed8; asc    E P  ;; 9: len 8; hex 8000000000000002; asc         ;; 10: len 8; hex 8000000000000003; asc         ;; 11: len 8; hex 80000000000034db; asc       4 ;;

トランザクション分離

トランザクションの分離についてインターネットで読みました。

グラスフィッシュでは、トランザクションの分離レベルを設定できます。私はそれを読み取り非コミットにしました。

うまくいかなかったので、mysqlに同じレベルを設定しました:

mysql> SELECT @@global.tx_isolation;
+-----------------------+
| @@global.tx_isolation |
+-----------------------+
| READ-UNCOMMITTED      | 
+-----------------------+
1 row in set (0.00 sec)

mysql> SELECT @@tx_isolation;
+------------------+
| @@tx_isolation   |
+------------------+
| READ-UNCOMMITTED | 
+------------------+
1 row in set (0.00 sec)

SVP 誰かが私に何が問題なのか教えてもらえますか? 本当にわからない!!!!

ところで、私はインターネットで見たことがありますが、リクエストごとにトランザクション分離レベルを選択できます... JPA でメソッドのトランザクション分離レベルを直接設定することは可能ですか? グローバルなデータ更新 (15000 サービスのデクリメントなど) を行うジョブのみを読み取り非コミットにする必要があると思いますが、間違っていますか?

4

3 に答える 3

4

あなたの問題に対する正確な答えはありませんが、これは問題を絞り込むのに役立つかもしれません。

innodb は「コミットされていない読み取り」でも更新にロックを設定するため、デッドロックはどのトランザクション分離レベルでも発生する可能性があります。

これは、次の簡単なシナリオでテストできます。

CREATE TABLE locktest (a INT(11), b INT(11), PRIMARY KEY (a)) ENGINE=INNODB;
INSERT INTO locktest VALUE (1, 1);
INSERT INTO locktest VALUE (2, 1);

次に、2 つの mysql コンソール (C1 および C2) を開き、次のコマンドを順番に実行します。

C1> BEGIN;
C2> BEGIN;
C1> UPDATE locktest SET b = b + 1 WHERE a = 1;
C2> UPDATE locktest SET b = b + 1 WHERE a = 2;
C1> UPDATE locktest SET b = b + 1 WHERE a = 2;
C2> UPDATE locktest SET b = b + 1 WHERE a = 1;

C2 でデッドロックが発生し、コミットされていない読み取りでも C1 が正常に完了します。エンジン ログを確認すると、同様のレポートが表示されます。

テーブルの主キーを削除すると、コマンドはさらに早くチョークします。これは、ロックを設定しているクエリをカバーするインデックスがある場合、innodb ロックがより適切に機能するためです。

それで、あなたの問題に戻ります。

デッドロックで終了したトランザクションに関連するすべてのクエリをチェックし、適切なインデックスが存在することを確認する必要があります。MySQL が完全なテーブル スキャンを実行する必要がある場合、必要以上のロックが終了します。

これらのヒントは、アプリのデッドロックを解決するのに役立ちました。デッドロックを防ぐ良い方法は、「SELECT ... FOR UPDATE」で書き込みロックを設定して、親行をロックすることです。

したがって、たとえば、特定の顧客データを更新しようとする複数のトランザクションがある場合、「SELECT id FROM customer WHERE id=123 FOR UPDATE」を発行できます。トランザクションは、相互に必要なロックの保持を終了する代わりに、その時点で順番に待機します。 .

于 2009-05-06T03:00:39.200 に答える
0

私は、Oracle データベースを使用する Java アプリケーションで同様の問題を抱えていました。

データベースに外部キーのインデックスがないことがわかりました。これにより、データベースは必要以上に多くの行をロックし、高度な同時実行テストでデッドロックが発生しました。

これは、これを診断するための非常に優れた記事です。記事の多くはオラクル固有のものですが、他のリレーショナル データベースにも当てはまるものもあります: http://www.oratechinfo.co.uk/deadlocks.html

于 2010-10-25T16:37:38.370 に答える
-6

何をしても、トランザクションデータベースでデッドロックが発生することは当然のことです。それらを適切に処理し、失敗したトランザクションを一定の回数繰り返す必要があります(通常は3回で問題ありません)。Glassfishのどこかにそれを担当するパラメーターがあるはずです。

于 2009-05-31T17:34:47.753 に答える