Hibernate читает грязные данные в MySQL

77
9

У меня есть следующий код в Java (Servlet) в функции (@Transactional(rollbackFor = Exception.class) с помощью @Transactional(rollbackFor = Exception.class):

ModelAccount account = hibernateSession.get(**Class<T> type**, **int id**, new LockOptions(LockMode.PESSIMISTIC_WRITE));

Когда я запускаю эту функцию одновременно через 2 разных потока, я замечаю, что первый поток, который проходит эту строку, получает, например, (account.balance = 100). Вторая нить зависает, пока первая не зафиксируется. В моем случае первый поток обновляет баланс до 200. Когда возобновляется закрытый поток, он по-прежнему считывает account.balance = 100 вместо нового значения.

    Мой уровень изоляции на mysql:

    SHOW GLOBAL VARIABLES LIKE 'tx_isolation';
    Variable_name Value
    tx_isolation REPEATABLE-READ
    Без спящего режима не указано.

EDIT. Если я вручную заблокирую строку учетной записи, то спящий код все равно действует одинаково, даже если уровень изоляции READ-COMMITED на уровне базы данных и на уровне @Transactional(rollbackFor = Exception.class, isolation = Isolation.READ_COMMITTED) режима @Transactional(rollbackFor = Exception.class, isolation = Isolation.READ_COMMITTED)

Последнее, когда выполнение выполняется, я замечаю в журналах следующее:

Hibernate: select id from accounts where id =? for update

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

спросил(а) 2021-01-25T14:40:09+03:00 5 месяцев назад
1
Решение
76

Технически, это не грязные данные. Грязные чтения возможны только на READ_UNCOMMITTED, так что вы видите здесь повторяемые чтения, в то время как вместо этого вы хотите читать не повторяющиеся чтения.

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

Причина, по которой вторая транзакция видит значение 100 связана с уровнем изоляции REPEATABLE_READ. InnoDB использует MVCC, и в REPEATABLE_READ он гарантирует, что вы увидите записи, как если бы они были, когда транзакция началась.

Если вы хотите прочитать последнее значение, вам нужно переключиться на READ_COMMITTED.

Но даже в READ_COMMITTED вы все равно можете получить значение 100 если Hibernate уже кэшировал этот объект в кеше первого уровня. Hibernate обеспечивает повторяемые чтения на уровне приложений, поэтому, как только вы загружаете объект, вы получите одну и ту же ссылку на сущность независимо от того, сколько раз вы пытаетесь загрузить ее.

Обновить

Убедитесь, что @Transactional изоляции @Transactional принят во внимание. Если вы используете JTA, это может быть проигнорировано.

Если Hibernate ранее загрузил объект, он не перезагрузит его, если вы не выполните обновление.

ModelAccount account = hibernateSession.refresh(ModelAccount.class, id, new LockOptions(LockMode.PESSIMISTIC_WRITE));

ответил(а) 2021-01-25T14:40:09+03:00 5 месяцев назад
45

Если вы используете аннотацию @Transactional, убедитесь, что у вас есть правильная стратегия распространения. Взгляните на раздел 16.5.7 в документации: http://docs.spring.io/spring/docs/current/spring-framework-reference/html/transaction.html.

Вы можете решить проблему, указав стратегию распространения REQUIRES_NEW.

ответил(а) 2021-01-25T14:40:09+03:00 5 месяцев назад
Ваш ответ
Введите минимум 50 символов
Чтобы , пожалуйста,
Выберите тему жалобы:

Другая проблема