Шпора по транзакциям на языке 1С
Данные > Статьи и инструкции > Практика
Шпаргалка по статье Сергея Старых (tormozit) о работе транзакций в 1С:Предприятии 8.3: https://infostart.ru/1c/articles/1026771/
Главная идея: важно различать платформенную транзакцию 1С, транзакцию СУБД и счётчик вложенности транзакций; ошибка внутри транзакции может не только вызвать исключение, но и пометить текущую транзакцию как неотменяемую для дальнейшей работы («откат только»).
Ключевое: наличие блока Попытка…Исключение не означает, что после ошибки можно безопасно работать дальше в той же транзакции. Сначала нужно проверить её состояние и, если она испорчена, отменить.
Обычный безопасный каркас: начать транзакцию, выполнить операцию, зафиксировать; при исключении - отменить и повторно пробросить ошибку. Не стоит без необходимости вызывать ОтменитьТранзакцию() в обработчике, если вы не знаете, была ли транзакция начата именно в текущем методе. Метод может выполняться внутри внешней транзакции вызывающего кода.
Базовый шаблон:
НачатьТранзакцию();
Попытка
// Работа в транзакции
ЗафиксироватьТранзакцию();
Исключение
ОтменитьТранзакцию();
// Если ошибку надо передать выше
ВызватьИсключение;
КонецПопытки;
Попытка
// Работа в транзакции
ЗафиксироватьТранзакцию();
Исключение
ОтменитьТранзакцию();
// Если ошибку надо передать выше
ВызватьИсключение;
КонецПопытки;
Ошибка не всегда равна простому исключению. Некоторые ошибки становятся критичными для текущей транзакции. Например:
- ошибка SQL-запроса;
- ошибка записи объекта;
- блокировка или конфликт конкурентного доступа;
- ошибка проведения/записи, связанная с обращением к данным;
- отдельные ошибки платформы и СУБД.
После такой ошибки транзакция может быть помечена как неотменяемая. Даже если исключение обработано, последующие обращения к данным, запись объектов или фиксация могут завершиться сообщением: В данной транзакции уже происходили ошибки.
Обычный безопасный каркас: начать транзакцию, выполнить операцию, зафиксировать; при исключении - отменить и повторно пробросить ошибку.
Проверка состояния:
// Метод для проверки транзакции "В данной транзакции уже происходили ошибки" (работает на глобальном контексте)
Функция ПроверитьОтменуТранзакции() Экспорт
Запрос = Новый Запрос("ВЫБРАТЬ 1");
Попытка
Запрос.Выполнить();
Возврат Ложь;
Исключение
Возврат Истина;
КонецПопытки;
КонецФункции
Функция ПроверитьОтменуТранзакции() Экспорт
Запрос = Новый Запрос("ВЫБРАТЬ 1");
Попытка
Запрос.Выполнить();
Возврат Ложь;
Исключение
Возврат Истина;
КонецПопытки;
КонецФункции
Проверяем, можно ли ещё продолжать работу.
Не передавайте объект ошибки «как есть»:
// Получаем информацию об ошибке и без исключения в транзакции проверяем отменяемость транзакции с помощью глобальной функции "В данной транзакции уже происходили ошибки"
// Переходим в метод ИнформацияОбОшибке() можно не выходя из транзакции и передаем в модуль менеджера, если информация была получена в нем.
// Функция в методе модуля менеджера объекта ИнформацияОбОшибке. Использовать эту функцию только в том случае если исключение в транзакции обрабатывается в модуле менеджера.
Функция ИнформацияОбОшибкеБезИсключения(Ошибка) Экспорт
Если ПроверитьОтменуТранзакции() Тогда
Результат = "" + Ошибка.ПодробноеОписание();
Иначе
Результат = Ошибка;
КонецЕсли;
Возврат Результат;
КонецФункции
// Переходим в метод ИнформацияОбОшибке() можно не выходя из транзакции и передаем в модуль менеджера, если информация была получена в нем.
// Функция в методе модуля менеджера объекта ИнформацияОбОшибке. Использовать эту функцию только в том случае если исключение в транзакции обрабатывается в модуле менеджера.
Функция ИнформацияОбОшибкеБезИсключения(Ошибка) Экспорт
Если ПроверитьОтменуТранзакции() Тогда
Результат = "" + Ошибка.ПодробноеОписание();
Иначе
Результат = Ошибка;
КонецЕсли;
Возврат Результат;
КонецФункции
И использование:
Исключение
ИнформацияОбОшибке();
ИнформацияОбОшибке("Ошибка", ИнформацияОбОшибкеКлиента.Ошибка, ОписаниеОшибкиБезИсключения(Ошибка));
//...
ВызватьИсключение;
КонецПопытки;
ИнформацияОбОшибке();
ИнформацияОбОшибке("Ошибка", ИнформацияОбОшибкеКлиента.Ошибка, ОписаниеОшибкиБезИсключения(Ошибка));
//...
ВызватьИсключение;
КонецПопытки;
Смысл: до логирования и передачи ошибки убедиться, что сама операция обработки ошибки не вызовет новую проблему в уже повреждённой транзакции.
Вложенные транзакции
Вложенность в 1С - это прежде всего работа со счётчиком транзакций, а не полноценные независимые SQL-транзакции уровня SAVEPOINT.
- Первый НачатьТранзакцию() открывает транзакцию.
- Вложенный вызов увеличивает счётчик.
- Внутренняя фиксация уменьшает счётчик, но реальная фиксация в СУБД произойдёт только при возврате счётчика к нулю.
- Внутренняя отмена также влияет на общий контекст; она не создаёт автономную точку отката для внутреннего метода.
- Если внешний метод уже начал транзакцию, внутренний метод не должен бездумно завершать её так, будто она принадлежит ему.
Практическое правило: метод должен понимать, он владелец транзакции или лишь работает в транзакции вызывающего кода.
Безопасный внутренний метод
Если метод может запускаться как самостоятельно, так и внутри уже открытой транзакции, полезно:
- Определять, есть ли активная транзакция.
- Открывать свою транзакцию только при необходимости.
- При ошибке откатывать только ту транзакцию, за жизненный цикл которой отвечает этот метод.
- Не скрывать критическую ошибку от вызывающего кода.
Логика выглядит так:
- Нет внешней транзакции → метод начинает, фиксирует или отменяет свою.
- Есть внешняя транзакция → метод не должен неожиданно фиксировать весь внешний сценарий.
- Ошибка сделала общую транзакцию неотменяемой → вызывающий уровень должен получить информацию об ошибке и корректно выполнить откат.
Пример, ошибка запроса:
Процедура ТестОшибкаВТранзакции1() Экспорт
НачатьТранзакцию(); // Счетчик = 1
Попытка
Ссылка = Справочники.Номенклатура.Элемент1;
НаименованиеСтрокой = "" + Ссылка; // Получаем строку для передачи в обработчик исключения. Она может быть преобразована функцией в дто.
НачатьТранзакцию(); // Счетчик = 2
Попытка
Запрос = Новый Запрос("ВЫБРАТЬ 1/0");
Запрос.Выполнить();
ЗафиксироватьТранзакцию(); // Счетчик = 2
Исключение
ОтменитьТранзакцию(); // Счетчик = 2
//ИнформацияОбОшибке("Ошибка", ИнформацияОбОшибкеКлиента.Ошибка, Ошибка); // Так получим дополнительную ошибку, если информация была получена в дто
ИнформацияОбОшибке("Ошибка", ИнформацияОбОшибкеКлиента.Ошибка, ИнформацияОбОшибкеБезИсключения(Ошибка));
Сообщить("Произошла ошибка дто - элемент справочника """ + НаименованиеСтрокой + """");
ВызватьИсключение;
КонецПопытки;
ЗафиксироватьТранзакцию(); // Счетчик = 1
Исключение
ОтменитьТранзакцию(); // Счетчик = 1
Сообщить("Произошла ошибка дто");
ВызватьИсключение;
КонецПопытки;
КонецПроцедуры
НачатьТранзакцию(); // Счетчик = 1
Попытка
Ссылка = Справочники.Номенклатура.Элемент1;
НаименованиеСтрокой = "" + Ссылка; // Получаем строку для передачи в обработчик исключения. Она может быть преобразована функцией в дто.
НачатьТранзакцию(); // Счетчик = 2
Попытка
Запрос = Новый Запрос("ВЫБРАТЬ 1/0");
Запрос.Выполнить();
ЗафиксироватьТранзакцию(); // Счетчик = 2
Исключение
ОтменитьТранзакцию(); // Счетчик = 2
//ИнформацияОбОшибке("Ошибка", ИнформацияОбОшибкеКлиента.Ошибка, Ошибка); // Так получим дополнительную ошибку, если информация была получена в дто
ИнформацияОбОшибке("Ошибка", ИнформацияОбОшибкеКлиента.Ошибка, ИнформацияОбОшибкеБезИсключения(Ошибка));
Сообщить("Произошла ошибка дто - элемент справочника """ + НаименованиеСтрокой + """");
ВызватьИсключение;
КонецПопытки;
ЗафиксироватьТранзакцию(); // Счетчик = 1
Исключение
ОтменитьТранзакцию(); // Счетчик = 1
Сообщить("Произошла ошибка дто");
ВызватьИсключение;
КонецПопытки;
КонецПроцедуры
Что здесь важно запомнить:
- Вложенная транзакция не изолирует ошибку от внешней.
- Ошибка
ВЫБРАТЬ 1/0делает продолжение работы рискованным. - Ошибку надо передавать так, чтобы не вызвать новый сбой.
- Верхний уровень должен корректно отменить свой контекст.infostart
Если ошибка произошла при записи объекта, ситуация обычно опаснее, чем при чисто вычислительной ошибке: платформа уже могла начать операции с данными и блокировками.
Ошибка записи объекта:
НачатьТранзакцию();
Попытка
Объект = Справочники.Номенклатура.Элемент1.ПолучитьОбъект();
Объект.Наименование = Строка(Дата());
Объект.Записать();
Запрос = Новый Запрос("ВЫБРАТЬ 1/0");
Запрос.Выполнить(); // Ошибка меняет флаг отменяемости транзакции
Исключение
Сообщить("Произошла ошибка дто");
ВызватьИсключение;
КонецПопытки;
ЗафиксироватьТранзакцию(); // Ошибка отменяемости транзакции, т.к. счетчик равен 0 и флаг = Истина
Попытка
Объект = Справочники.Номенклатура.Элемент1.ПолучитьОбъект();
Объект.Наименование = Строка(Дата());
Объект.Записать();
Запрос = Новый Запрос("ВЫБРАТЬ 1/0");
Запрос.Выполнить(); // Ошибка меняет флаг отменяемости транзакции
Исключение
Сообщить("Произошла ошибка дто");
ВызватьИсключение;
КонецПопытки;
ЗафиксироватьТранзакцию(); // Ошибка отменяемости транзакции, т.к. счетчик равен 0 и флаг = Истина
Если после ошибки выполнить ЗафиксироватьТранзакцию(), можно получить ошибку фиксации: текущая транзакция уже признана некорректной. Поэтому после критической ошибки нужен ОтменитьТранзакцию(), а не попытка довести выполнение до штатной фиксации.
Для сценариев записи важно после перехвата исключения проверять, не испорчена ли транзакция:
Обработка при записи:
Процедура ТестЗапись(Отказ)
Объект.Наименование = Строка(Дата());
О = Справочники.Номенклатура.Элемент1.ПолучитьОбъект();
О.Реквизит1 = Строка(Дата());
О.Записать();
Попытка
Запрос = Новый Запрос("ВЫБРАТЬ 1/0");
Запрос.Выполнить(); // Ошибка меняет флаг отменяемости транзакции
Исключение
КонецПопытки;
Если Транзакция1.ПроверитьОтменяемостьТранзакции() Тогда
ОтменитьТранзакцию(); // Уменьшаем счетчик транзакции. Так как он равен 0 и изменен флаг отмены, транзакция отменяется.
// Передаем ошибку пользователю с отмененной транзакцией. ИнформацияОбОшибке получает объект ошибки и вышла.
КонецЕсли;
// Записываем дто отмененной транзакции
О = Справочники.Номенклатура.Элемент1.ПолучитьОбъект();
О.Наименование = Строка(Дата());
О.Записать();
// Проверяем флаг и не записываем, т.к. транзакция дто отменена
Если Не Транзакция1.ПроверитьОтменяемостьТранзакции() Тогда
ЗафиксироватьТранзакцию(); // Счетчик выйдет из вложенной транзакции, пока внешняя транзакция делает запись
КонецЕсли;
КонецПроцедуры
Объект.Наименование = Строка(Дата());
О = Справочники.Номенклатура.Элемент1.ПолучитьОбъект();
О.Реквизит1 = Строка(Дата());
О.Записать();
Попытка
Запрос = Новый Запрос("ВЫБРАТЬ 1/0");
Запрос.Выполнить(); // Ошибка меняет флаг отменяемости транзакции
Исключение
КонецПопытки;
Если Транзакция1.ПроверитьОтменяемостьТранзакции() Тогда
ОтменитьТранзакцию(); // Уменьшаем счетчик транзакции. Так как он равен 0 и изменен флаг отмены, транзакция отменяется.
// Передаем ошибку пользователю с отмененной транзакцией. ИнформацияОбОшибке получает объект ошибки и вышла.
КонецЕсли;
// Записываем дто отмененной транзакции
О = Справочники.Номенклатура.Элемент1.ПолучитьОбъект();
О.Наименование = Строка(Дата());
О.Записать();
// Проверяем флаг и не записываем, т.к. транзакция дто отменена
Если Не Транзакция1.ПроверитьОтменяемостьТранзакции() Тогда
ЗафиксироватьТранзакцию(); // Счетчик выйдет из вложенной транзакции, пока внешняя транзакция делает запись
КонецЕсли;
КонецПроцедуры
Если ошибка сделала транзакцию неотменяемой, её необходимо завершить откатом до выполнения действий, которые требуют рабочей транзакции: повторной записи, регистрации в логах, формирования служебных данных и т. п.
Эти подходы сохраняют целостность данных и уменьшают число дефектов.
0
комментарии
____________________
Перепечатка текста и фотографий разрешена при наличии прямой ссылки на источник