Что происходит с таблицей после ошибки во второй операции транзакции
Что проверяют
Знаете ли вы, что ошибка в середине транзакции сама по себе ничего не откатывает, и кто именно принимает решение об откате: сервер, драйвер или ваш код. Вопрос отделяет тех, кто выучил ACID по определению, от тех, кто разбирал реальный SqlTransaction.
Как отвечать
Начните с разделения понятий. Атомарность обещает, что зафиксировано будет всё или ничего, но она не обещает, что откат случится сам.
- В SQL Server при выключенном
SET XACT_ABORT(умолчание для большинства драйверов) обычная ошибка вроде нарушения ограничения отменяет только сбойный оператор. Транзакция остаётся открытой, изменения первой операции живы и видны вашей же сессии. - Решение принимает приложение. Поймали исключение и всё равно вызвали
Commit, значит первая операция зафиксируется, и таблица останется с половиной изменений. - Тяжёлые ошибки переводят транзакцию в нефиксируемое состояние:
XACT_STATE()вернёт минус единицу, и любая команда кроме отката упадёт. - Спасает шаблон
using:DisposeбезCommitоткатывает транзакцию, поэтому непойманное исключение приводит к корректному откату. - Частичный откат делают точками сохранения:
tran.Save("p1")иtran.Rollback("p1").
Про EF Core скажите, что SaveChanges сам оборачивает набор команд в транзакцию и откатывает её при ошибке, но состояние трекера при этом не чинится: сущности остаются помеченными как изменённые, и повторный вызов отправит их снова.
Где спотыкаются
Отвечают, что СУБД автоматически откатит всю транзакцию при любой ошибке. Это поведение режима XACT_ABORT ON, а по умолчанию сервер отменяет только сбойный оператор, и открытая транзакция продолжает держать блокировки, пока приложение не решит, что с ней делать.
Говорят, что до фиксации изменения лежат в памяти и в базу не попадают. Попадают: страницы меняются, запись уходит в журнал, а невидимость для чужих сессий обеспечивают изоляция и блокировки, а не отложенная запись.
Ловят исключение, логируют и идут дальше по методу, не вызвав откат. Соединение возвращается в пул с незакрытой транзакцией, и следующий запрос получает чужие блокировки или ошибку о нефиксируемом состоянии. Откат нужен явный, лучше через using или finally.