При работе с базой данных 1С 7.7 в SQL-режиме может возникнуть ситуация, когда во время выполнения сложной операции, обернутой в транзакцию, происходит ошибка взаимоблокировки (deadlock, SQL State: 40001). При этом может сложиться впечатление, что часть операций внутри транзакции была успешно сохранена в базе, а часть — нет. Это поведение противоречит основному принципу транзакций — атомарности, согласно которому транзакция должна быть либо полностью выполнена, либо полностью отменена.
Давайте разберемся, почему возникает такая иллюзия "частичной фиксации" и как правильно организовать код, чтобы гарантировать целостность данных при любых ошибках, включая deadlock.
Прежде чем перейти к коду, важно понять теоретическую основу. Это поможет развеять основные заблуждения.
Атомарность транзакций. Это фундаментальный принцип. Он означает, что любая транзакция, начатая командой НачатьТранзакцию(), является неделимой операцией. Не существует такого понятия, как "частично зафиксированная транзакция". Либо все изменения, сделанные между НачатьТранзакцию() и ЗафиксироватьТранзакцию(), сохраняются в базе данных, либо, в случае вызова ОтменитьТранзакцию() или критической ошибки, все они полностью отменяются (откатываются).
Что такое deadlock? Взаимоблокировка (deadlock) — это ситуация, когда два (или более) процесса в SQL Server блокируют друг друга. Например, Процесс 1 заблокировал Таблицу А и ждет доступа к Таблице Б, в то время как Процесс 2 заблокировал Таблицу Б и ждет доступа к Таблице А. Ни один из них не может продолжаться, и они будут ждать вечно.
Как SQL Server решает проблему deadlock? SQL Server не позволяет процессам "висеть" бесконечно. Он имеет встроенный механизм мониторинга, который обнаруживает такие тупиковые ситуации. Обнаружив deadlock, сервер принудительно выбирает один из процессов "жертвой" (victim). Транзакция этого процесса-жертвы полностью откатывается, а самому процессу возвращается ошибка с кодом 1205 (SQL State 40001). Второй процесс, "выживший", успешно продолжает свою работу, как будто ничего не произошло.
Таким образом, сам SQL Server гарантирует, что в случае deadlock транзакция-жертва будет полностью отменена.
Если SQL Server откатывает транзакцию, то почему в 1С мы видим обратную картину? Например, в рамках одной операции нужно было создать элемент справочника и два документа, а в итоге в базе появился справочник и первый документ, а второй — нет. Причина почти всегда кроется в коде 1С, а не в поведении SQL Server.
Основная причина — неявные (скрытые) транзакции.
Рассмотрим "неправильный" код:
// Неправильный пример без общей транзакции!
Спр = СоздатьОбъект("Справочник.Номенклатура");
Спр.Новый();
Спр.Наименование = "Товар 1";
Спр.Записать(); // <-- Успешно выполнилось в своей неявной транзакции и зафиксировалось
Док1 = СоздатьОбъект("Документ.ПриходнаяНакладная");
Док1.Новый();
// ...заполнение документа...
Док1.Записать(); // <-- Успешно выполнилось в своей неявной транзакции и зафиксировалось
Док2 = СоздатьОбъект("Документ.РасходнаяНакладная");
Док2.Новый();
// ...заполнение документа...
Док2.Записать(); // <-- Здесь произошел deadlock с другим процессом.
// SQL откатил эту ОДНУ операцию, 1С получила исключение.
В этом примере каждая команда Записать() выполняется в своей собственной, отдельной мини-транзакции. Если deadlock случается на записи третьего объекта (Док2), то первые два уже давно и успешно сохранены в базе данных. В результате мы получаем именно ту картину "частичной фиксации", которая вводит в заблуждение.
Чтобы гарантировать атомарность всей бизнес-операции, все связанные изменения данных должны быть обернуты в одну явную транзакцию. А для корректной обработки ошибок, которые могут возникнуть внутри, необходимо использовать конструкцию Попытка...Исключение.
Это единственно верный и надежный способ работы с транзакциями в 1С 7.7.
Рассмотрим правильный шаблон кода:
// Начинаем общую транзакцию для всей группы операций
НачатьТранзакцию();
Попытка
// === Начало защищенного блока ===
// Все наши действия выполняются здесь
Спр = СоздатьОбъект("Справочник.Номенклатура");
Спр.Новый();
Спр.Наименование = "Товар 1";
Спр.Записать();
Док1 = СоздатьОбъект("Документ.ПриходнаяНакладная");
Док1.Новый();
// ...заполнение документа...
Док1.Записать();
Док2 = СоздатьОбъект("Документ.РасходнаяНакладная");
Док2.Новый();
// ...заполнение документа...
Док2.Записать(); // <-- Если deadlock произойдет здесь...
// Если все операции выше прошли без ошибок, фиксируем транзакцию
ЗафиксироватьТранзакцию();
Сообщить("Операция успешно завершена!");
Исключение
// ...то выполнение немедленно перейдет сюда, в блок Исключение.
// ОБЯЗАТЕЛЬНО отменяем транзакцию, чтобы откатить ВСЕ изменения,
// сделанные с момента вызова НачатьТранзакцию()
ОтменитьТранзакцию();
// Сообщаем пользователю или записываем в лог подробности ошибки
Сообщить("Произошла ошибка! Все изменения отменены. " + ОписаниеОшибки());
КонецПопытки;
Проанализируем по шагам, как работает этот код:
НачатьТранзакцию(): Мы открываем транзакцию в SQL Server. Все последующие команды записи (Записать(), Провести() и т.д.) будут выполняться в контексте этой единственной транзакции.Попытка: Начинается блок кода, в котором отслеживаются любые исключительные ситуации.Попытка последовательно выполняются все наши действия. На этом этапе изменения видны только внутри нашей текущей транзакции, но для других пользователей они еще не зафиксированы.Док2.Записать()) SQL Server вернет ошибку deadlock, 1С сгенерирует исключение. Выполнение кода внутри блока Попытка немедленно прекращается. Код, следующий за строкой, вызвавшей ошибку (включая ЗафиксироватьТранзакцию()), не будет выполнен.Исключение: Управление передается в этот блок.ОтменитьТранзакцию(): Это ключевой момент. Мы явно даем команду 1С отправить в SQL Server команду ROLLBACK. Сервер отменяет абсолютно все изменения, сделанные с момента вызова НачатьТранзакцию(), включая запись справочника и первого документа. База данных возвращается в то состояние, в котором она была до начала операции.ОписаниеОшибки(): Эта полезная функция возвращает текст ошибки, который пришел от SQL Server, что позволяет точно диагностировать проблему.Используя такой подход, вы можете быть на 100% уверены, что ваша бизнес-операция будет атомарной. Либо все три объекта будут успешно записаны в базу, либо, в случае любой ошибки, ни один из них не будет сохранен.