Spring Hibernate @Data обновление против необработанного запроса UPDATE: исключение оптимистической блокировки, несмотря на @Transactional? - PullRequest
0 голосов
/ 08 мая 2019

Я работаю с Spring Hibernate ORM для управления данными с СУБД MYSQL, используя мое приложение Java Spring.У меня есть простое требование - когда кто-то использует мое приложение, я увеличу счетчик в моей базе данных на 1, чтобы я мог отслеживать количество использований моих пользователей.

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

Вот мой первый пример кода.При стресс-тесте создается исключение OptimisticLockException.

@Transactional
public void updateLog() {
    ConnectionUseLog log = getLog();
    log.setCount(log.getCount() + 1);
    // ... other unrelated updates to database
}

Здесь ConnectionUseLog - это класс @Data с атрибутом @Version для оптимистической блокировки и атрибутом count (это то, что я увеличиваю на 1).

@Data
public class ConnectionUseLog {
    @Version
    @Column(name="optlock_version")
    Integer version;
    @Column(name="count")
    Integer count;
}
  • getLog () - это необработанный запрос SELECT, который я написал, получая нужную мне строку.Он использует интерфейс PagingAndSortingRepository из springframework.data.repository.
  • getCount (...) получает значение в столбце, метод генерируется автоматически.
  • setCount (...) обновляет счетчик,Метод генерируется автоматически.

Теперь в следующем примере это гораздо проще, проще и проще выполнить необработанный запрос UPDATE (а не SELECT, как раньше) к базе данных - никаких проблем.Необработанный запрос также внедряется через интерфейс PagingAndSortingRepository из springframework.data.repository, как в предыдущем примере.

@Transactional
public void updateDailyLog() {
    incrementCount(1);
    // ... other unrelated updates to database
}

Почему возникают проблемы с оптимистичным параллелизмом, когда оба они заключены в @Transactional?

Может кто-нибудь помочь понять разницу и помочь мне взвесить оба подхода?Спасибо.

1 Ответ

0 голосов
/ 08 мая 2019

Я не уверен, почему вы думаете, что @Transactional должен заставить ваш OptimisticLockingException уйти. Оптимистическая блокировка - это параллельность различных транзакций.

Он работает путем увеличения версии в каждой транзакции, а также проверки обновлений о том, что версия по-прежнему не изменилась с момента загрузки объекта.

При высокой нагрузке это часто не удается для серии событий, подобных этой:

  1. Транзакция 1 (T1) загружает строку, скажем, с версией 23.
  2. Транзакция 2 (T2) загружает ту же строку с той же версией 23.
  3. T1 обновляет счет, увеличивая значение до 24, и фиксирует изменение этого значения видимым для других транзакций.
  4. T2 также пытается обновить строку, но версия больше не 23, поэтому вы получаете исключение оптимистической блокировки.
  5. Вам следует перезапустить T2, пока он не сможет фактически обновить базу данных.

Обратите внимание, что ни одна из транзакций не блокирует блокировку строки. В большинстве случаев это хорошо, поскольку повышает производительность. Эта выгода исчезнет, ​​если число 4 часто встречается.

Когда вы делаете «сырое ОБНОВЛЕНИЕ», которое, как мне кажется, выглядит примерно так: UPDATE log set counter = counter + 1 WHERE ..., оптимистическая блокировка не происходит. Вместо этого вы используете в основном пессимистическую блокировку: вы блокируете строку, которую хотите обновить, когда читаете ее. Это происходит неявно, так как чтение и запись происходят в одном выражении. Это означает, что похожий процесс, описанный выше, теперь будет происходить следующим образом:

  1. Транзакция 1 (T1) обновляет строку, блокирующую ее.
  2. Транзакция 2 (T2) пытается обновить ту же строку, но блокируется блокировкой и вынуждена ждать.
  3. T1 фиксирует свою транзакцию, делая изменения видимыми для других транзакций и снимая блокировку.
  4. T2 теперь может продолжить и обновить строку на основе измененного значения от T1.

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

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

...