InnoDB тупик с чтением-без связи! - Java - Glassfish - EJB3 (JPA / Hibernate) - PullRequest
2 голосов
/ 28 апреля 2009

Уже несколько дней у меня возникают проблемы с тупиком в Java-приложении с Glassfish - EJB3 с Mysql InnoDB

Config: Mysql InnoDB: Ver 14.12 Distrib 5.0.51a, для debian-linux-gnu (i486) с использованием readline 5.2

Сервер приложений: 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;
    }

Эта функция отлично работает, когда она не тестируется под нагрузкой, и, например, у нас есть 1 запрос каждые 5 секунд.

Во время нагрузочного тестирования у нас есть задания, работающие на высоких частотах (например, каждые 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 ;;

Изоляция транзакции

Я читаю в Интернете информацию об изоляции транзакций.

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

Это не сработало, тогда я установил в 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 сервисов), должны быть свободны от чтения, не так ли?

Ответы [ 3 ]

4 голосов
/ 06 мая 2009

У меня нет точного ответа на вашу проблему, но это может помочь вам сузить ее.

Взаимные блокировки могут возникать на любом уровне изоляции транзакций, потому что 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», в этот момент они будут ждать по порядку, а не заканчивать удержание блокировок, которые друг другу нужны.

0 голосов
/ 25 октября 2010

У меня была похожая проблема с приложением Java, использующим базу данных Oracle.

Я обнаружил, что в базе данных отсутствовали индексы на внешних ключах, из-за чего база данных блокировала намного больше строк, чем требовалось, и приводила к взаимным блокировкам в высококонкурентном тесте.

Вот очень хорошая статья для диагностики этого. Большая часть статьи относится к оракулам, но некоторые вещи применимы и к другим реляционным базам данных: http://www.oratechinfo.co.uk/deadlocks.html

0 голосов
/ 31 мая 2009

Вы должны принять как должное, что в любой транзакционной базе данных будут возникать взаимоблокировки, независимо от того, что вы делаете. Вы просто должны изящно обработать их и повторить неудачную транзакцию некоторое фиксированное количество раз (обычно 3 в порядке) - где-то в Glassfish должен быть параметр, отвечающий за это.

...