Почему @Transactional и Propagation.REQUIRES_NEW не спасают от долгого лока строки
Вопрос звучит просто: метод А в транзакции вызывает метод Б с REQUIRES_NEW. Что произойдёт с локами строк, которые уже держит внешняя транзакция?
Ответ, который чаще всего дают — «внутренняя транзакция изолирована, работает со своими локами». Это правда только наполовину. REQUIRES_NEW действительно приостанавливает внешнюю транзакцию и открывает новую физическую транзакцию в БД. Но если внешняя транзакция уже держит лок на строку, а внутренняя пытается обновить ту же строку — она встанет и будет ждать, пока внешняя транзакция не закоммитится или не откатится.
При этом внешняя транзакция приостановлена логически на уровне Spring, но физически соединение с БД у неё занято — она не отпустит лок, пока не завершится. Получается ожидание внутри одного и того же потока выполнения кода.
@Transactional public void outer(Long orderId) { Order order = repo.findById(orderId); order.setStatus("PROCESSING"); repo.save(order);
inner.updateSameRow(orderId); }
@Transactional( propagation = Propagation.REQUIRES_NEW ) public void updateSameRow(Long orderId) { // ждёт лок outer(), который // сам его вызвал repo.updateStatus(orderId, "DONE"); }
Следом спрашивают: это deadlock? Нет, это просто самозаблокировка на время таймаута транзакции — если оба вызова происходят в одном потоке, соединение внешней транзакции не освобождается, пока не выполнится вызов inner, а inner ждёт лок, который держит именно это соединение. Итог — либо lock timeout, либо зависание на весь transaction timeout.
REQUIRES_NEW изолирует транзакцию логически, но не избавляет от конкуренции за одну и ту же строку в БД.
Тренажёр: 600 вопросов, мок с таймером, план повторов
senior·base — что спрашивают на самом деле
В этом посте были ссылки, но мы их удалили по правилам Сетки