1с ошибка обращения данным при транзакции
Привет, Хабр! Представляю вашему вниманию перевод статьи «Error and Transaction Handling in SQL Server. Part One – Jumpstart Error Handling» автора Erland Sommarskog.
1. Введение
Эта статья – первая в серии из трёх статей, посвященных обработке ошибок и транзакций в SQL Server. Её цель – дать вам быстрый старт в теме обработки ошибок, показав базовый пример, который подходит для большей части вашего кода. Эта часть написана в расчете на неопытного читателя, и по этой причине я намеренно умалчиваю о многих деталях. В данный момент задача состоит в том, чтобы рассказать как без упора на почему. Если вы принимаете мои слова на веру, вы можете прочесть только эту часть и отложить остальные две для дальнейших этапов в вашей карьере.
С другой стороны, если вы ставите под сомнение мои рекомендации, вам определенно необходимо прочитать две остальные части, где я погружаюсь в детали намного более глубоко, исследуя очень запутанный мир обработки ошибок и транзакций в SQL Server. Вторая и третья части, так же, как и три приложения, предназначены для читателей с более глубоким опытом. Первая статья — короткая, вторая и третья значительно длиннее.
Все статьи описывают обработку ошибок и транзакций в SQL Server для версии 2005 и более поздних версий.
1.1 Зачем нужна обработка ошибок?
Почему мы обрабатываем ошибки в нашем коде? На это есть много причин. Например, на формах в приложении мы проверяем введенные данные и информируем пользователей о допущенных при вводе ошибках. Ошибки пользователя – это предвиденные ошибки. Но нам также нужно обрабатывать непредвиденные ошибки. То есть, ошибки могут возникнуть из-за того, что мы что-то упустили при написании кода. Простой подход – это прервать выполнение или хотя бы вернуться на этап, в котором мы имеем полный контроль над происходящим. Недостаточно будет просто подчеркнуть, что совершенно непозволительно игнорировать непредвиденные ошибки. Это недостаток, который может вызвать губительные последствия: например, стать причиной того, что приложение будет предоставлять некорректную информацию пользователю или, что еще хуже, сохранять некорректные данные в базе. Также важно сообщать о возникновении ошибки с той целью, чтобы пользователь не думал о том, что операция прошла успешно, в то время как ваш код на самом деле ничего не выполнил.
Мы часто хотим, чтобы в базе данных изменения были атомарными. Например, задача по переводу денег с одного счета на другой. С этой целью мы должны изменить две записи в таблице CashHoldings и добавить две записи в таблицу Transactions. Абсолютно недопустимо, чтобы ошибки или сбой привели к тому, что деньги будут переведены на счет получателя, а со счета отправителя они не будут списаны. По этой причине обработка ошибок также касается и обработки транзакций. В приведенном примере нам нужно обернуть операцию в BEGIN TRANSACTION и COMMIT TRANSACTION, но не только это: в случае ошибки мы должны убедиться, что транзакция откачена.
2. Основные команды
Мы начнем с обзора наиболее важных команд, которые необходимы для обработки ошибок. Во второй части я опишу все команды, относящиеся к обработке ошибок и транзакций.
2.1 TRY-CATCH
Основным механизмом обработки ошибок является конструкция TRY-CATCH, очень напоминающая подобные конструкции в других языках. Структура такова:
Если какая-либо ошибка появится в , выполнение будет переведено в блок CATCH, и будет выполнен код обработки ошибок.
Как правило, в CATCH откатывают любую открытую транзакцию и повторно вызывают ошибку. Таким образом, вызывающая клиентская программа понимает, что что-то пошло не так. Повторный вызов ошибки мы обсудим позже в этой статье.
Вот очень быстрый пример:
Результат выполнения: This is the error: Divide by zero error encountered.
Мы вернемся к функции error_message() позднее. Стоит отметить, что использование PRINT в обработчике CATCH приводится только в рамках экспериментов и не следует делать так в коде реального приложения.
Есть одно очень важное ограничение у конструкции TRY-CATCH, которое нужно знать: она не ловит ошибки компиляции, которые возникают в той же области видимости. Рассмотрим пример:
Как можно видеть, блок TRY присутствует, но при возникновении ошибки выполнение не передается блоку CATCH, как это ожидалось. Это применимо ко всем ошибкам компиляции, таким как пропуск колонок, некорректные псевдонимы и тому подобное, которые возникают во время выполнения. (Ошибки компиляции могут возникнуть в SQL Server во время выполнения из-за отложенного разрешения имен – особенность, благодаря которой SQL Server позволяет создать процедуру, которая обращается к несуществующим таблицам.)
Эти ошибки не являются полностью неуловимыми; вы не можете поймать их в области, в которой они возникают, но вы можете поймать их во внешней области. Добавим такой код к предыдущему примеру:
Теперь мы получим на выходе это:
На этот раз ошибка была перехвачена, потому что сработал внешний обработчик CATCH.
2.2 SET XACT_ABORT ON
Оно активирует два параметра сессии, которые выключены по умолчанию в целях совместимости с предыдущими версиями, но опыт доказывает, что лучший подход – это иметь эти параметры всегда включенными. Поведение SQL Server по умолчанию в той ситуации, когда не используется TRY-CATCH, заключается в том, что некоторые ошибки прерывают выполнение и откатывают любые открытые транзакции, в то время как с другими ошибками выполнение последующих инструкций продолжается. Когда вы включаете XACT_ABORT ON, почти все ошибки начинают вызывать одинаковый эффект: любая открытая транзакция откатывается, и выполнение кода прерывается. Есть несколько исключений, среди которых наиболее заметным является выражение RAISERROR.
Параметр XACT_ABORT необходим для более надежной обработки ошибок и транзакций. В частности, при настройках по умолчанию есть несколько ситуаций, когда выполнение может быть прервано без какого-либо отката транзакции, даже если у вас есть TRY-CATCH. Мы видели такой пример в предыдущем разделе, где мы выяснили, что TRY-CATCH не перехватывает ошибки компиляции, возникшие в той же области. Открытая транзакция, которая не была откачена из-за ошибки, может вызвать серьезные проблемы, если приложение работает дальше без завершения транзакции или ее отката.
Для надежной обработки ошибок в SQL Server вам необходимы как TRY-CATCH, так и SET XACT_ABORT ON. Среди них инструкция SET XACT_ABORT ON наиболее важна. Если для кода на промышленной среде только на нее полагаться не стоит, то для быстрых и простых решений она вполне подходит.
Выше я использовал синтаксис, который немного необычен. Большинство людей написали бы два отдельных выражения:
Между ними нет никакого отличия. Я предпочитаю версию с SET и запятой, т.к. это снижает уровень шума в коде. Поскольку эти выражения должны появляться во всех ваших хранимых процедурах, они должны занимать как можно меньше места.
3. Основной пример обработки ошибок
После того, как мы посмотрели на TRY-CATCH и SET XACT_ABORT ON, давайте соединим их вместе в примере, который мы можем использовать во всех наших хранимых процедурах. Для начала я покажу пример, в котором ошибка генерируется в простой форме, а в следующем разделе я рассмотрю решения получше.
Для примера я буду использовать эту простую таблицу.
Вот хранимая процедура, которая демонстрирует, как вы должны работать с ошибками и транзакциями.
Первая строка в процедуре включает XACT_ABORT и NOCOUNT в одном выражении, как я показывал выше. Эта строка – единственная перед BEGIN TRY. Все остальное в процедуре должно располагаться после BEGIN TRY: объявление переменных, создание временных таблиц, табличных переменных, всё. Даже если у вас есть другие SET-команды в процедуре (хотя причины для этого встречаются редко), они должны идти после BEGIN TRY.
Причина, по которой я предпочитаю указывать SET XACT_ABORT и NOCOUNT перед BEGIN TRY, заключается в том, что я рассматриваю это как одну строку шума: она всегда должна быть там, но я не хочу, чтобы это мешало взгляду. Конечно же, это дело вкуса, и если вы предпочитаете ставить SET-команды после BEGIN TRY, ничего страшного. Важно то, что вам не следует ставить что-либо другое перед BEGIN TRY.
Часть между BEGIN TRY и END TRY является основной составляющей процедуры. Поскольку я хотел использовать транзакцию, определенную пользователем, я ввел довольно надуманное бизнес-правило, в котором говорится, что если вы вставляете пару, то обратная пара также должна быть вставлена. Два выражения INSERT находятся внутри BEGIN и COMMIT TRANSACTION. Во многих случаях у вас будет много строк кода между BEGIN TRY и BEGIN TRANSACTION. Иногда у вас также будет код между COMMIT TRANSACTION и END TRY, хотя обычно это только финальный SELECT, возвращающий данные или присваивающий значения выходным параметрам. Если ваша процедура не выполняет каких-либо изменений или имеет только одно выражение INSERT/UPDATE/DELETE/MERGE, то обычно вам вообще не нужно явно указывать транзакцию.
В то время как блок TRY будет выглядеть по-разному от процедуры к процедуре, блок CATCH должен быть более или менее результатом копирования и вставки. То есть вы делаете что-то короткое и простое и затем используете повсюду, не особо задумываясь. Обработчик CATCH, приведенный выше, выполняет три действия:
- Откатывает любые открытые транзакции.
- Повторно вызывает ошибку.
- Убеждается, что возвращаемое процедурой значение отлично от нуля.
не нужна, если нет явной транзакции в процедуре, но это абсолютно неверно. Возможно, вы вызываете хранимую процедуру, которая открывает транзакцию, но которая не может ее откатить из-за ограничений TRY-CATCH. Возможно, вы или кто-то другой добавите явную транзакцию через два года. Вспомните ли вы тогда о том, что нужно добавить строку с откатом? Не рассчитывайте на это. Я также слышу читателей, которые возражают, что если тот, кто вызывает процедуру, открыл транзакцию, мы не должны ее откатывать… Нет, мы должны, и если вы хотите знать почему, вам нужно прочитать вторую и третью части. Откат транзакции в обработчике CATCH – это категорический императив, у которого нет исключений.
Код повторной генерации ошибки включает такую строку:
Встроенная функция error_message() возвращает текст возникшей ошибки. В следующей строке ошибка повторно вызывается с помощью выражения RAISERROR. Это не самый простой способ вызова ошибки, но он работает. Другие способы мы рассмотрим в следующей главе.
Замечание: синтаксис для присвоения начального значения переменной в DECLARE был внедрен в SQL Server 2008. Если у вас SQL Server 2005, вам нужно разбить строку на DECLARE и выражение SELECT.
Финальное выражение RETURN – это страховка. RAISERROR никогда не прерывает выполнение, поэтому выполнение следующего выражения будет продолжено. Пока все процедуры используют TRY-CATCH, а также весь клиентский код обрабатывает исключения, нет повода для беспокойства. Но ваша процедура может быть вызвана из старого кода, написанного до SQL Server 2005 и до внедрения TRY-CATCH. В те времена лучшее, что мы могли делать, это смотреть на возвращаемые значения. То, что вы возвращаете с помощью RETURN, не имеет особого значения, если это не нулевое значение (ноль обычно обозначает успешное завершение работы).
Последнее выражение в процедуре – это END CATCH. Никогда не следует помещать какой-либо код после END CATCH. Кто-нибудь, читающий процедуру, может не увидеть этот кусок кода.
После прочтения теории давайте попробуем тестовый пример:
Давайте добавим внешнюю процедуру для того, чтобы увидеть, что происходит при повторном вызове ошибки:
4. Три способа генерации ошибки
4.1 Использование error_handler_sp
Позвольте представить вам error_handler_sp:
Первое из того, что делает error_handler_sp – это сохраняет значение всех error_xxx() функций в локальные переменные. Я вернусь к выражению IF через секунду. Вместо него давайте посмотрим на выражение SELECT внутри IF:
Вот как обработчик CATCH должен выглядеть, когда вы используете error_handler_sp:
Давайте попробуем несколько тестовых сценариев.
4.2. Использование ;THROW
В SQL Server 2012 Microsoft представил выражение ;THROW для более легкой обработки ошибок. К сожалению, Microsoft сделал серьезную ошибку при проектировании этой команды и создал опасную ловушку.
С выражением ;THROW вам не нужно никаких хранимых процедур. Ваш обработчик CATCH становится таким же простым, как этот:
Если у вас SQL Server 2012 или более поздняя версия, измените определение insert_data и outer_sp и попробуйте выполнить тесты еще раз. Результат в этот раз будет такой:
Имя процедуры и номер строки верны и нет никакого другого имени процедуры, которое может нас запутать. Также сохранены оригинальные номера ошибок.
Нельзя отрицать того, что ;THROW имеет свои преимущества, но точка с запятой не единственная ловушка этой команды. Если вы хотите использовать ее, я призываю вас прочитать по крайней мере вторую часть этой серии, где я раскрываю больше деталей о команде ;THROW. До этого момента, используйте error_handler_sp.
4.3. Использование SqlEventLog
Третий способ обработки ошибок – это использование SqlEventLog, который я описываю очень детально в третьей части. Здесь я лишь сделаю короткий обзор.
Для использования SqlEventLog, ваш обработчик CATCH должен быть таким:
@@procid возвращает идентификатор объекта текущей хранимой процедуры. Это то, что SqlEventLog использует для логирования информации в таблицу. Используя те же тестовые сценарии, получим результат их работы с использованием catchhandler_sp:
5. Финальные замечания
Вы изучили основной образец для обработки ошибок и транзакций в хранимых процедурах. Он не идеален, но он должен работать в 90-95% вашего кода. Есть несколько ограничений, на которые стоит обратить внимание:
Перед тем как закончить, я хочу кратко коснуться триггеров и клиентского кода.
Триггеры
Пример для обработки ошибок в триггерах не сильно отличается от того, что используется в хранимых процедурах, за исключением одной маленькой детали: вы не должны использовать выражение RETURN (потому что RETURN не допускается использовать в триггерах).
С триггерами важно понимать, что они являются частью команды, которая запустила триггер, и в триггере вы находитесь внутри транзакции, даже если не используете BEGIN TRANSACTION.
Иногда я вижу на форумах людей, которые спрашивают, могут ли они написать триггер, который не откатывает в случае падения запустившую его команду. Ответ таков: нет способа сделать это надежно, поэтому не стоит даже пытаться. Если в этом есть необходимость, по возможности не следует использовать триггер вообще, а найти другое решение. Во второй и третьей частях я рассматриваю обработку ошибок в триггерах более подробно.
Клиентский код
У вас должна быть обработка ошибок в коде клиента, если он имеет доступ к базе. То есть вы должны всегда предполагать, что при любом вызове что-то может пойти не так. Как именно внедрить обработку ошибок, зависит от конкретной среды.
Здесь я только обращу внимание на важную вещь: реакцией на ошибку, возвращенную SQL Server, должно быть завершение запроса во избежание открытых бесхозных транзакций:
6. Конец первой части
Это конец первой из трех частей серии. Если вы хотели изучить вопрос обработки ошибок быстро, вы можете закончить чтение здесь. Если вы настроены идти дальше, вам следует прочитать вторую часть, где наше путешествие по запутанным джунглям обработки ошибок и транзакций в SQL Server начинается по-настоящему.
Всем доброго дня!
Выполняю обновление базы БП 30 88.32 на релиз 89.43 (пробовал и на 89.47). В конфигураторе обнова ставится без каких-либо предупреждений и ошибок, но при запуске предприятия обновление прерывается на 40% с ошибкой:
В данной транзакции уже происходили ошибки!
: НаборЗаписей.Прочитать();
: УстановитьСтатусОбработчика(Обработчик.Процедура, "Ошибка", ТекстОшибки);
: ВыполнитьОбработчикОбновления(Обработчик, ПараметрыОбработчика, ДополнительныеПараметры);
: ИтерацияОбновления.ВыполненныеОбработчики = ВыполнитьИтерациюОбновления(ИтерацияОбновления, Параметры);
: ВыполнитьДействияПриОбновленииИнформационнойБазы(ПараметрыОбновления, ДополнительныеПараметры);
: Результат = ВыполнитьОбновлениеИнформационнойБазы(ПараметрыОбновления);
:ОбновлениеИнформационнойБазыСлужебный.ВыполнитьОбновлениеИнформационнойБазыВФоне(Параметры[0],Параметры[1])
: Выполнить ИмяМетода + "(" + ПараметрыСтрока + ")";
: ОбщегоНазначения.ВыполнитьМетодКонфигурации(ИмяПроцедуры, ПараметрыВызова);
: ВызватьПроцедуру(ВсеПараметры.ИмяПроцедуры, ВсеПараметры.ПараметрыПроцедуры);
по причине:
В данной транзакции уже происходили ошибки!
Также, пробовал переводить базу из БП 20 (66.128) в 30 - такая же ситуация, что и описана выше.
Просьба помочь в данном вопросе.
- в более ранних обновлениях не отработали обработчики обновлений! До обновления проверьте чтобы все обработчики обновлений завершены
(8) 1) что в журнале регистрации после ошибки в базе 30 - указано в шапке темы.
2) При переводе другой базы из 20 в 30 - восстановил базу и запустил обнволение - жду завершения
(8) Это после появления ошибки при выполнении обновления в режиме Предприятие (при переводе базы из 20 в 30)
В данной транзакции уже происходили ошибки!
: НаборЗаписей.Прочитать();
: УстановитьСтатусОбработчика(Обработчик.Процедура, "Ошибка", ТекстОшибки);
: ВыполнитьОбработчикОбновления(Обработчик, ПараметрыОбработчика, ДополнительныеПараметры);
: ИтерацияОбновления.ВыполненныеОбработчики = ВыполнитьИтерациюОбновления(ИтерацияОбновления, Параметры);
: ВыполнитьДействияПриОбновленииИнформационнойБазы(ПараметрыОбновления, ДополнительныеПараметры);
: Результат = ВыполнитьОбновлениеИнформационнойБазы(ПараметрыОбновления);
:ОбновлениеИнформационнойБазыСлужебный.ВыполнитьОбновлениеИнформационнойБазыВФоне(Параметры[0],Параметры[1])
: Выполнить ИмяМетода + "(" + ПараметрыСтрока + ")";
: ОбщегоНазначения.ВыполнитьМетодКонфигурации(ИмяПроцедуры, ПараметрыВызова);
: ВызватьПроцедуру(ВсеПараметры.ИмяПроцедуры, ВсеПараметры.ПараметрыПроцедуры);
по причине:
В данной транзакции уже происходили ошибки!
(12)При вызове обработчика обновления:
"ЗарплатаКадрыОбновлениеСПредыдущейРедакции.ЗарплатаКадрыОбновлениеС20()"
произошла ошибка:
"Запись не верна! Значение поля "Физическое лицо" не может быть пустым!: ВидыЗанятостиСотрудников: 01.10.2018 0:00:00, (106:a1988c89a558e82c11e5938326ec0d86), ООО "Организация", (Регистр сведений: Виды занятости сотрудников; Номер строки: 1)
: ДокументПереноса.Движения.ВидыЗанятостиСотрудников.Записать();
: ЗарегистрироватьВидЗанятостиСотрудника(ДанныеВыгружаемогоДокумента, СоответствиеКонвертированныхОбъектов, НовыйРегистратор);
: КонверитроватьДанныеОВидеЗанятостиСотрудникаДокумента(ДанныеВыгружаемогоДокумента, СоответствиеКонвертированныхОбъектов, НовыйРегистратор);
: КонвертироватьДвиженияПриемаНаРаботу(ДанныеВыгружаемогоДокумента, СоответствиеКонвертированныхОбъектов, КонвертированныйДокумент.ПолучитьОбъект());
: КонвертироватьДокументыПриемНаРаботу(СписокДокументовПриемНаРаботу(), СоответствиеКонвертированныхОбъектов);
:ЗарплатаКадрыОбновлениеСПредыдущейРедакции.ЗарплатаКадрыОбновлениеС20()
: Выполнить ИмяМетода + "(" + ПараметрыСтрока + ")";
: ОбщегоНазначения.ВыполнитьМетодКонфигурации(Обработчик.Процедура, ПараметрыОбработчика);
: ВыполнитьОбработчикОбновления(Обработчик, ПараметрыОбработчика, ДополнительныеПараметры);
: ИтерацияОбновления.ВыполненныеОбработчики = ВыполнитьИтерациюОбновления(ИтерацияОбновления, Параметры);
: ВыполнитьДействияПриОбновленииИнформационнойБазы(ПараметрыОбновления, ДополнительныеПараметры);
: Результат = ВыполнитьОбновлениеИнформационнойБазы(ПараметрыОбновления);
:ОбновлениеИнформационнойБазыСлужебный.ВыполнитьОбновлениеИнформационнойБазыВФоне(Параметры[0],Параметры[1])
: Выполнить ИмяМетода + "(" + ПараметрыСтрока + ")";
: ОбщегоНазначения.ВыполнитьМетодКонфигурации(ИмяПроцедуры, ПараметрыВызова);
: ВызватьПроцедуру(ВсеПараметры.ИмяПроцедуры, ВсеПараметры.ПараметрыПроцедуры);
по причине:
Запись не верна! Значение поля "Физическое лицо" не может быть пустым!: ВидыЗанятостиСотрудников: 01.10.2018 0:00:00, (106:a1988c89a558e82c11e5938326ec0d86), ООО "Организация", (Регистр сведений: Виды занятости сотрудников; Номер строки: 1)".
Нужно восстановить базу из бэкапа и удалить эту запись в РС?
Запись не верна! Значение поля "Физическое лицо" не может быть пустым!: ВидыЗанятостиСотрудников: 01.10.2018 0:00:00, (106:a1988c89a558e82c11e5938326ec0d86), ООО "Организация", (Регистр сведений: Виды занятости сотрудников; Номер строки: 1)".
Нужно восстановить базу из бэкапа и удалить эту запись в РС
(14) Подскажите пожалуйста, в другой базе 30 перед ошибкой о прерывании обновления есть ошибка:
При вызове обработчика обновления:
"Справочники.РегламентированныеОтчеты.ЗаполнитьСписокРегламентированныхОтчетов()"
произошла ошибка:
"Значение "002074" поля "Код" не уникально
: Объект.Записать();
: ОбновлениеИнформационнойБазы.ЗаписатьОбъект(ТекЭлемент, , Истина);
: ОбработкаОбновлениеОтчетов.ЗаполнитьСписокОтчетов(ДеревоОтчетов);
:Справочники.РегламентированныеОтчеты.ЗаполнитьСписокРегламентированныхОтчетов()
: Выполнить ИмяМетода + "(" + ПараметрыСтрока + ")";
: ОбщегоНазначения.ВыполнитьМетодКонфигурации(Обработчик.Процедура, ПараметрыОбработчика);
: ВыполнитьОбработчикОбновления(Обработчик, ПараметрыОбработчика, ДополнительныеПараметры);
: ИтерацияОбновления.ВыполненныеОбработчики = ВыполнитьИтерациюОбновления(ИтерацияОбновления, Параметры);
: ВыполнитьДействияПриОбновленииИнформационнойБазы(ПараметрыОбновления, ДополнительныеПараметры);
: Результат = ВыполнитьОбновлениеИнформационнойБазы(ПараметрыОбновления);
:ОбновлениеИнформационнойБазыСлужебный.ВыполнитьОбновлениеИнформационнойБазыВФоне(Параметры[0],Параметры[1])
: Выполнить ИмяМетода + "(" + ПараметрыСтрока + ")";
: ОбщегоНазначения.ВыполнитьМетодКонфигурации(ИмяПроцедуры, ПараметрыВызова);
: ВызватьПроцедуру(ВсеПараметры.ИмяПроцедуры, ВсеПараметры.ПараметрыПроцедуры);
по причине:
Значение "002074" поля "Код" не уникально".
Ошибки базы данных и транзакции
При работе с базой данных могут происходить ошибки. В 1С:Предприятии 8 ошибки базы данных подразделяются на следующие две категории:
К невосстановимым относятся такие ошибки базы данных, при возникновении которых функционирование 1С:Предприятия 8 может быть серьезно нарушено. Поэтому, во избежание более серьезных неприятностей (например, порчи данных), при возникновении невосстановимой ошибки выполнение 1С:Предприятия 8 прекращается. Если невосстановимая ошибка базы данных произошла в процессе выполнения транзакции, то все изменения сделанные в рамках этой транзакции отменяются.
При возникновении восстановимой ошибки считается, что серьезных нарушений в работе 1С:Предприятия 8 не произошло и работа может быть продолжена, но сама вызвавшая ошибку операция прекращается, и вызывается исключение, которое может быть перехвачено и обработано средствами встроенного языка. Казалось бы, все понятно. Но есть тонкость. Если восстановимая ошибка базы данных произошла в процессе выполнения транзакции, то, вне зависимости от того, было исключение, вызванное этой ошибкой, перехвачено и обработано или нет, транзакция уже не может быть продолжена или зафиксирована. Единственная операция с базой данных, которую можно произвести в такой ситуации - это отмена транзакции. Таким образом, приведенный фрагмент кода не вполне корректен:
Если при выполнении оператора Данные.Записать() произойдет восстановимая ошибка базы данных (например, по причине того, что элемент данных был заблокирован другим пользователем), то вызванное этой ошибкой исключение будет перехвачено, но повторное выполнение этого же оператора в цикле уже безусловно приведет к ошибке, так как при выполнении данной транзакции уже имела место ошибка базы данных. Следует заметить, что не всякая ошибка приводит к невозможности продолжения выполнения и фиксации транзакции, а именно ошибка базы данных. Исключения, не имеющие отношения к ошибкам базы данных, никакого влияния на возможность продолжения выполнения транзакции не оказывают.
Как же выйти из такой ситуации? Общее правило состоит в следующем: если при выполнении транзакции имели место ошибки базы данных, то следует отменить всю транзакцию в целом и, в случае необходимости, повторить попытку ее выполнения с самого начала. Таким образом, приведенный выше пример должен быть переработан следующим образом:
В данном варианте, если при выполнении оператора Данные.Записать() произойдет восстановимая ошибка базы данных, то при обработке исключения, вызванного этой ошибкой, транзакция будет отменена и при повторном выполнении цикла транзакция будет начата сначала.
Следует однако сделать предостережение. Дело в том, что в рамках уже выполняемой транзакции можно обращаться к методам НачатьТранзакцию() , ЗафиксироватьТранзакцию() и ОтменитьТранзакцию() . Однако вызов метода НачатьТранзакцию() при уже выполняющейся транзакции не означает начала новой транзакции. В этом случае просто произойдет увеличение на 1 значения внутреннего счетчика транзакций. Метод НачатьТранзакцию() начинает новую транзакцию только в том случае, если значение внутреннего счетчика транзакций равно 0. Аналогично, обращение к методам ЗафиксироватьТранзакцию() или ОтменитьТранзакцию() приводит к реальному завершению транзакции только в том случае, если значение внутреннего счетчика транзакций равно 1. Если при значении счетчика транзакций большем 1 произойдет обращение к методу ЗафиксироватьТранзакцию() , то значение счетчика будет просто уменьшено на 1:
Если же при значении счетчика траназкций большем 1 произойдет обращение к методу ОтменитьТранзакцию() , то значение счетчика транзакций не только будет уменьшено на 1, но и произойдет установка признака, не позволяющего зафиксировать результаты выполнения всей транзакции в целом. И последующее обращение к методу ЗафиксироватьТранзакцию() , выполняемое при значении счетчика транзакций равном 1, фактически приведет к отмене транзакции:
Аналогично, ошибка базы данных, произошедшая при значении счетчика транзакций большем 1, приведет к невозможности продолжения и фиксации всей транзакции в целом.
Доброго времени суток!
Вопрос в следующем:
При выполнении регламентного задания (в клиент-серверной версии базы, выполнение на сервере) возникла ошибка "В данной транзакции уже происходили ошибки!", и, естественно, при каждом последующем запуске оно вылетает на эту же ошибку, даже не пытаясь ничего реально запускать.
Я сталкивалась с подобной ошибкой при выполнении кода на клиенте, и в этом случае спасало просто перезайти в базу - дальше все работало. А если на стороне сервера возникает эта ошибка? Каждый раз перезагружать сервер никто не даст.
Как сбросить эту ошибку, чтобы продолжить функционирование регламентного задания, почему ошибка могла возникнуть и как избежать ее повторения?
В основном такая штука случается, когда внутри одной транзакции оказывается другая, в которой собственно и присходит ошибка. Самый простой способ её увидеть:
Открываем транзакцию, начинаем проводить документы, причем проведение стоит в Попытка-Исключение, чтобы программа не вываливалась. Какой-то докумен не может быть проведен и выкидывает ошибку, после этого Зафиксировать транзакцию уже нельзя.
Вывод - либо отказаться от транзакции, либо от обработки ошибок. Я выбрал отказ от транзакции в таких случаях или закрываю транзакцию до записи объектов (особенно если они типовые).
> продолжить функционирование регламентного задания
Продолжить никак. Лучше всего его полностью остановить и запустить снова.
"Продолжить - я имела ввиду не текущее выполнение регламентного задания, а работу рег.задания при последующих запусках. Кого остановить? Рег.задание?
Поясните, пожалуйста, подробней.
Почему нельзя Зафиксировать транзакцию, если, к примеру, используется конструкция:
НачатьТранзакцию();
Попытка
// проводим докумены
Исключение
// обработка исключения
КонецПопытки;
ЗафиксироватьТранзакцию();
И что значит "отказаться от отработки ошибок"? То есть, убрать из этой конструкции Попытку-Исключение?"
"> Поясните, пожалуйста, подробней.
> Почему нельзя Зафиксировать транзакцию, если, к примеру, используется конструкция:
>
> НачатьТранзакцию();
> Попытка
> // проводим докумены
> Исключение
> // обработка исключения
> КонецПопытки;
> ЗафиксироватьТранзакцию();
>
> И что значит "отказаться от отработки ошибок"? То есть, убрать из этой конструкции Попытку-Исключение?
Представть, что ваша конструкция теперь выглядит так (просто есть кусок которого вы не видите - не вы его писали):
НачатьТранзакцию();
Попытка
//Вот этот код где-то там - далеко в каком-нибудь модуле, да и 1С, что-то такое ведет в случае проведения
НачатьТранзакцию();
Попытка
//Здесь возникает ошибка
Исключение
ОтменитьТранзакцию();
КонецПопытки;
ЗафиксироватьТранзакцию();
Исключение
// обработка исключения
КонецПопытки;
ЗафиксироватьТранзакцию();
Так вот, если я правильно понимаю, то если вложенная транзакция закрывается, то транзакция верхнего уровня это видит и отказывается закрываться (возможно она уже тоже закрытая). В общем - кроме отмены транзакции уже ничего не сделать.
Вот и получается, что несмотря на обработку ошибки (исключения) - ошибка уже была и ничего с этим не сделать. Значит надо от чего-то отказаться. Или увидеть ошибку или убрать транзакцию.
"
> Кого остановить? Рег.задание?
А что у вас показывает "Консоль Заданий " по поводу вашего зависшего задания?
"1> НачатьТранзакцию();
2> Попытка
3> //Вот этот код где-то там - далеко в каком-нибудь модуле, да и 1С, что-то такое ведет в случае проведения
4> НачатьТранзакцию();
5> Попытка
6> //Здесь возникает ошибка
7> Исключение
8> ОтменитьТранзакцию();
9> КонецПопытки;
10> ЗафиксироватьТранзакцию();
11> Исключение
12> // обработка исключения
13> КонецПопытки;
14> ЗафиксироватьТранзакцию();
В этой последовательности получается, что если мы попадаем на отмену транзакции в строке 8, то потом в строке 10 закрывается не внутренняя транзакция, а внешняя, и при закрытии ее повторно в 14 появляется ошибка. В данном случае, я бы предложила фиксацию внутренней транзакции перенести во внутреннюю попытку (в самый конец). Но это уже так, отступление.
Ок, в этой последовательности понятно, спасибо. Только будет очень трудно найти, где же оно в реальном коде ломается.
>> Кого остановить? Рег.задание?
>А что у вас показывает "Консоль Заданий " по поводу вашего зависшего задания?
Вот "КонсольЗаданий" как раз и показывала в графе Ошибка "В данной транзакции уже происходили ошибки!". Правда, потом убрали Использование Рег.задания, а сегодня опять его запустили - ошибка прошла. Но есть вероятность, что сервер перезагружали, узнать пока точно не могу - это сервер нашего филиала в другом городе. "
Последние несколько дней база стала давать предупреждение:При выполенении произошла ошибка! Таблица 1SJourn Ошибка обращения к данным при транзакиции, выполняемой другим пользователем.Повторить попытку? [Да Нет].Появляется при проведении доков, выполнении отчетов. Если кто встречал такую фичу, подскажите как лечить
Спасиб, я глянул. Насколько понял, коллизия призаписи доков в табицу, используемую для УРБД. Но, почему она стала появляться? Раньше ведь не было..
. Да тех, кто в бронепоезде: несколько пользователей хотят одновременно получить одни данные. Возникает транзакция (блокировка данных). Теперь-то понятно?
В 1SJORN хранятся журналы, все. Наиболее вероятно, что в одной из обработок проведения ты впендюрил Вопрос или Предупреждение. Обработка это транзакция, и она не закончится, пока пользователь не закроет окошко вызванное одной из этих функций. Пока один пользователь не закончит транзакцию, никто другой не может ее начать.
+ Разные пользователи пытаются обратиться к одним и тем-же данным, которые блокированы кем-то в транзакции.
епрст, пользователей сколько было, столько и осталось, раньше этого предупреждения не было, а сейчас све юзеры ко мне бегут дружной толпой и жалуются
Объясняю на пальцах, пока один пользователь проводит документ, другие пользователи ничего провести не могут, если у тебя где-то в проведении неэффективный алгоритм, т.е. док проводится 2-3 то все будут эти 2-3 минуты наблюдать эту надпись. Такое бывает если ты неэффективно рассчитываешь остатки например, пока доков было немного проводилось быстро, чем дальше - тем хуже. Либо вариант . Просто бери копию базы и тупо проводи доки по очереди, наблюдай который проводится слишком долго, или задает вопросы в процессе проведения.
+ Проводи по сетке, зачастую локально у тебя все летает, а по сети тормозит (если, например, запрос в цикл засунул - локально будет пару секунд проводится, а по сетке пару минут)
Кстати очень может быть, у меня в одном регистре висят огромные непроходящие минуса, из-за этого все доки долго черепятся
Даже наверняка. Еще сделай нехитрую вешь - поиск по всем текстам "НачатьТранзакцию", если какой-нить тяжелый отчет целиком в транзакцию сунул, те несколько минут пока он формируется все будут наблюдать эту же ошибку.
+ Короче с такой надписью всех блокирует кусок НачатьТранзакцию; . ЗафиксироватьТранзакцию; либо ОбработкаПроведения - это такая же транзакция, только явно не написано.
Осталось только оптимизировать, где-то многократные запросы, либо вместо итогов по движениям шаришься, чем больше движений - тем дольше проводится. Думай как сделать лучше.
Как вариант можно их в терминал загнать - проведение ускорится в разы, но код свой анализировать все же надо, где-то косяк.
Читайте также: