Транзакции: правила использования
Область применения: управляемое приложение, мобильное приложение, обычное приложение.
Транзакции применяются для целостного изменения связанных данных, т.е. все действия с базой данных, выполняемые в рамках транзакции или выполняются целиком, или целиком откатываются.
1. Использование транзакций в 1С:Предприятии обладает рядом особенностей:
не поддерживаются вложенные транзакции (см. подробнее Вложенность транзакций);
при возникновении исключения в общем случае транзакция не может быть зафиксирована – при этом не важно, было ли это исключение обработано или нет (см. подробнее Ошибки базы данных и транзакции, Особенности работы объектов при отмене транзакции);
транзакция может быть инициирована явно в прикладном коде при использовании метода НачатьТранзакцию . Так же платформа 1С:Предприятие неявным образом начинает транзакцию при любой записи в базу данных (см. подробнее Документация платформы. Механизм транзакций);
Эти особенности накладывают ряд требований к написанию кода с использованием транзакций. Несоблюдение этих требований может приводить к возникновению ошибок вида «В этой транзакции уже происходили ошибки», которые может быть крайне сложно воспроизвести и отладить.
1.1. Поскольку исключение не отменяет транзакцию сразу, но запрещает успешное завершение транзакции, то все вызовы НачатьТранзакцию с одной стороны и ЗафиксироватьТранзакцию или ОтменитьТранзакцию с другой стороны должны быть парными.
1.2. Начало транзакции и ее фиксация (отмена) должны происходить в контексте одного метода
Попытка
. // чтение или запись данных
ДокументОбъект.Записать()
ЗафиксироватьТранзакцию();
Исключение
ОтменитьТранзакцию();
. // дополнительные действия по обработке исключения
КонецПопытки;
Попытка
. // чтение или запись данных
ДокументОбъект.Записать()
ЗафиксироватьТранзакцию();
Исключение
ОтменитьТранзакцию();
. // дополнительные действия по обработке исключения
КонецПопытки;
1.3. При использовании транзакций необходимо предусмотреть обработку исключений, придерживаясь следующих правил:
метод НачатьТранзакцию должен быть за пределами блока Попытка-Исключение непосредственно перед оператором Попытка ;
все действия, выполняемые после вызова метода НачатьТранзакцию , должны находиться в одном блоке Попытка, в том числе чтение, блокировка и обработка данных;
метод ЗафиксироватьТранзакцию должен идти последним в блоке Попытка перед оператором Исключение , чтобы гарантировать, что после ЗафиксироватьТранзакцию не возникнет исключение;
необходимо предусмотреть обработку исключений – в блоке Исключение нужно сначала вызвать метод ОтменитьТранзакцию , а затем выполнять другие действия, если они требуются;
рекомендуется в блоке Исключение делать запись в журнал регистрации;
при использовании вложенных транзакций (см. п. 1.4) в конце блока Исключение рекомендуется добавить оператор ВызватьИсключение . В противном случае исключение не будет передано выше по стеку вызовов, там не сработает обработка исключения, внешняя транзакция не будет явным образом отменена и платформа вызовет исключение «В данной транзакции происходила ошибка»
НачатьТранзакцию();
Попытка
БлокировкаДанных = Новый БлокировкаДанных;
ЭлементБлокировкиДанных = БлокировкаДанных.Добавить(«Документ.ПриходнаяНакладная»);
ЭлементБлокировкиДанных.УстановитьЗначение(«Ссылка», СсылкаДляОбработки);
ЭлементБлокировкиДанных.Режим = РежимБлокировкиДанных.Исключительный;
БлокировкаДанных.Заблокировать();
. // чтение или запись данных
ЗаписьЖурналаРегистрации(НСтр(«ru = ‘Выполнение операции'»),
УровеньЖурналаРегистрации.Ошибка,
,
,
ОбработкаОшибок.ПодробноеПредставлениеОшибки(ИнформацияОбОшибке()));
ВызватьИсключение; // есть внешняя транзакция
1.4. Использование вложенных транзакций приводит к усложнению кода. Принимая решение об использовании этой возможности, нужно очень взвешенно оценить решаемую задачу: возможно, это усложнение просто не оправдано.
1.4.1. Не стоит усложнять код, явно используя метод НачатьТранзакцию , когда кроме записи объекта другие действия c базой данных не делаются – платформа при записи сама откроет транзакцию.
Не нужно явно открывать транзакцию тогда, когда не требуется выполнять ответственное чтение данных. Например, обычно ответственное чтение не требуется при записи нового объекта (нового набора записей регистра).
При использовании методов ПолучитьОбъект (или Прочитать для наборов записей) необходимо анализировать должно ли чтение быть отвественным и в зависимости от этого принимать решение о явном использовании метода НачатьТранзакцию .
Попытка
ДокументОбъект = Документы.ПриходнаяНакладная.СоздатьДокумент();
. // действия по заполнению объекта
ДокументОбъект.Записать();
Исключение
. // действия по обработке исключения
КонецПопытки;
НачатьТранзакцию();
Попытка
ДокументОбъект = Документы.ПриходнаяНакладная.СоздатьДокумент();
. // действия по заполнению объекта
ДокументОбъект.Записать();
ЗафиксироватьТранзакцию();
Исключение
ОтменитьТранзакцию();
КонецПопытки;
1.4.2. Если метод рассчитан на вызов только в рамках уже открытой транзакции (например, метод предназначен для вызова только из событий ПередЗаписью , ОбработкаПроведения и т.п.) в общем случае явным образом открывать в нем транзакцию не имеет никакого практического смысла.
1.4.3. При необходимости повысить качество сообщений об ошибках – на каждом уровне разработчик может предусмотреть свою обработку исключений, для чего, возможно, потребуется открыть вложенную транзакцию.
Пример. Вызывается метод ДобавитьЭлектроннуюПодпись . Внутри, если что-то пошло не так, нужно обработать исключение и добавить текст вида: «Не удалось добавить электронную подпись к объекту %ПредставлениеОбъекта% по причине:%ОписаниеОшибки%». В противном случае исключение будет обработано выше по стеку вызовов, например, при записи файла и будет выдано сообщение вида: «Не удалось записать файл %ИмяФайла% по причине: %ОписаниеОшибки%», где в «%ОписаниеОшибки%», будет просто указание на строчку кода и пользователю будет непонятно, зачем вообще программа записывала файл, если он просто его подписывал.
1.4.4. При обработке исключения, если транзакция все еще активна, например, исключение возникло во вложенной транзакции, нельзя обращаться к базе данных, так как это приведет к исключению «В этой транзакции уже происходили ошибки». При этом нужно учитывать, что обращение к базе данных может быть неявным, например, для получения представления ссылки.
2. Ограничение на длину (продолжительность) транзакции.
2.1. В общем случае в рамках одной транзакции нужно выполнять только те действия, которые неделимы, исходя из бизнес-логики.
Пример. При проведении документа записывается документ и его движения в регистрах. Если не прошла запись хотя бы в один регистр вся операция проведения должна быть отменена.
2.1.1. Если с точки зрения бизнес-логики действия могут быть выполнены по отдельности, то их в общем случае не следует объединять в одну транзакцию.
2.1.2. Исключением из п.2.1.1 могут быть случаи, когда с целью оптимизации несколько несвязанных объектов обрабатываются в рамках одной транзакции. В этом случае необходимо взвешенно подходить к выбору порции обработки данных: нужно стремиться к достижению золотой середины между длительностью одной транзакции и объемом фиксируемых данных с одной стороны и количеством транзакций с другой.
2.2. Следует избегать транзакций, которые выполняются длительное время.
Например, неправильно : для загрузки адресного классификатора записывать все данные, относящиеся к одной версии классификатора в одной транзакции, для того, чтобы в случае ошибки откатить целиком загружаемую версию классификатора. Т.к. данных по одной версии классификатора много (объем около 1 Гб), то для выполнения такой транзакции, во-первых, может не хватить оперативной памяти (особенно при использовании файловой информационной базы на 32-разрядной ОС), а, во-вторых, такая операция будет выполняться достаточно долго и ее нельзя будет оптимизировать за счет выполнения в несколько потоков.
Правильно: разбить загрузку новой версии классификатора на небольшие порции так, чтобы запись порции в одной транзакции не превышала 20 секунд в условиях высоконагруженной информационной системы и реализовать функциональность по откату к предыдущей версии в случае ошибки. Максимальная продолжительность указана исходя из того, что время ожидания установки транзакционной блокировки данных в информационной базе по умолчанию равно 20 сек.
2.2.1 Чем дольше выполняется транзакция, тем большее время будут заняты ресурсы сервера 1С:Предприятия и СУБД. Как правило длинные транзакции занимают следующие ресурсы:
- в ходе выполнения транзакции все изменения в базе данных записываются в журнал транзакций, что необходимо для возможности откатить транзакцию;
- блокировки, установленные в транзакции, остаются до конца транзакции;
- на сервере 1С:Предприятия блокировки занимают оперативную память;
- другие ресурсы, необходимые самой бизнес-логике, которая выполняется в транзакции.
Все это в целом может снижать эффективность использования ресурсов.
2.2.2. Если две транзакции пересекаются по блокируемым ресурсам, то транзакция, которая начала выполняться позже, будет ожидать возможность установления блокировки ограниченное время (по умолчанию – 20 секунд), после чего будет завершена с исключением «Превышено время ожидания установки блокировки». Поэтому длинные транзакции могут сильно снижать удобство параллельной работы пользователей.
Возникновение таких исключений – это повод провести анализ действий, которые выполняются в конфликтующих транзакциях
возможно, какие-то действия можно вынести за транзакцию (см. п. 2.4);
если действие вынести нельзя, то нужно постараться оптимизировать алгоритм его выполнения;
также нужно проанализировать оптимальность устанавливаемых блокировок (см. группу стандартов Избыточные блокировки и методы оптимизации )
2.3. В рамках транзакции нужно стремиться выполнять минимум действий – только те, которые нельзя в соответствии с бизнес-логикой выполнять вне транзакции. В частности:
сложные, ресурсоемкие расчеты нужно стремиться делать до начала транзакции, если это позволяет бизнес-логика;
если расчет должен выполняться в транзакции, то нужно стремиться сделать его как можно более простым. Например, контроль остатков можно делать уже после записи простым запросом к записываемому регистру;
проверка заполнения объекта должна делаться вне транзакции (см. Проверки, выполняемые в и вне транзакции записи объекта);
запросы, перед выполнением которых не нужно устанавливать блокировку данных, нужно стремиться выполнять до начала транзакции (см. Ответственное чтение данных);
запросы, выполняемые в рамках транзакций нужно стремиться оптимизировать (см. группу стандартов Оптимизация запросов )
не следует в транзакции вызывать внешние ресурсы (сетевые папки, интранет- и интернет-сайты, почтовые, FTP-, HTTP-, веб-сервисы и т.п.):
обращение к внешнему ресурсу может быть длительным;
ресурс может быть недоступен;
может произойти ошибка тайм-аута.
3. Обязательное использование транзакции.
3.1. В случае, если для ускорения операции записи в регистр используется отключение итогов, такую операцию вместе с отключением и включением итогов необходимо выполнять в транзакции, иначе в других сеансах может возникнуть ошибка при получении среза последних.
НаборЗаписей = РегистрыСведений.КурсыВалют.СоздатьНаборЗаписей();
НаборЗаписей.Отбор.Валюта.Установить(ВалютаСсылка);
НаборЗаписей.Загрузить(ТаблицаКурсов);
НаборЗаписей.ОбменДанными.Загрузка = Истина;
НачатьТранзакцию();
Попытка
РегистрыСведений.КурсыВалют.УстановитьИспользованиеИтогов(Ложь);
НаборЗаписей = РегистрыСведений.КурсыВалют.СоздатьНаборЗаписей();
НаборЗаписей.Отбор.Валюта.Установить(ВалютаСсылка);
НаборЗаписей.Загрузить(ТаблицаКурсов);
НаборЗаписей.ОбменДанными.Загрузка = Истина;
ЗафиксироватьТранзакцию();
Исключение
ОтменитьТранзакцию();
ВызватьИсключение;
КонецПопытки;
Транзакции в 1С
В этом материале мы расскажем о «зоне ответственности» транзакций «1С». О том, что такое блокировки и взаимоблокировки мы уже писали, так вот, данные действия являются прямым следствием транзакций «1С».
Что же из себя представляет транзакция в 1С?
По определению, транзакция в 1С это последовательность действий, которая переводит базу данных из одного целостного состояния в другое целостное состояние. К примеру — процесс перевода денег с одной банковской карты на другую.
Итог транзакций может быть диаметрально противоположен: они либо выполняются до конца, либо в принципе невыполнимы. Такого понятия как «частично выполненная транзакция» нет. В СУБД транзакции фиксируются с помощью COMMIT.
Свойства транзакций
Обозначим наиболее часто встречающие требования к транзакциям «1С» — ACID (Atomicity, Consistency, Isolation, Durability).
Есть перечень свойств, которые должны быть у каждой транзакции:
1. Атомарность (неделимость).
Суть ее состоит в том, чтобы по окончании транзакции все сведения были согласованы. Ведь даже если добавили незначительную запись, то вполне может возникнуть рассогласование.
Рассмотрим пример, когда происходит попытка внести запись вне транзакции. Т.е. вначале пользователь добавляет запись в основную таблицу, а потом в индексы. При этом возможно будет внести новые данные лишь в первый индекс. Во второй сделать это будет проблематично, могут возникнуть непредвиденные помехи (к примеру, отключится электропитание).
Новые сведения получат статус «несогласованных» – т.е. в таблице все отражено, а в индексе нет информации. На этом этапе и «подключается» атомарность для того, чтобы те или иные действия были завершены.
2. Изоляция.
Данное свойство «отвечает» за параллельный режим работы пользователей и помогает предотвратить потерю общих данных. К примеру, может возникнуть ситуация, при которой два пользователя вносят изменения в один документ, и, как результат, стирают изменения друг друга. Здесь и приходит на помощь изоляция.
Главная ее задача – защищать информацию от других транзакций. Блокировки при этом обеспечивают изоляцию транзакции.
Cпособы создания транзакции
1. Автоматический. К примеру, обработать проведение документа или запись элемента справочника в информационную базу. Увидеть, активна ли транзакция или нет, поможет опция «Транзакция активна()».
2. Самостоятельный. (Т.е., когда ее создает разработчик). Пошагово это выглядит так: «Начать Транзакцию()», в заключении «Зафиксировать Транзакцию()», либо (при необходимости) «Отменит Транзакцию()».
Отметим, что вложенные транзакции 1С не поддерживает. Если пользователь периодически открывает транзакцию, она «объединяется» и «превращается» в одну. Если проводится фиксация или отмена, то это распространяется на все транзакции, которые были активированы до этого.
Возможно, что вас не устраивает производительность «1С». Повысить скорость возможно с помощью верно созданной транзакции. Если пользователь все проводит правильно, то не будет блокировок и взаимоблокировок. Не будут отображаться ошибки, и система не будет отменять проведение документа.
1С 8.2 УП : Использование транзакций
Использование транзакций в 1С
Транзакция предназначена для записи в информационную базу согласованных изменений. Все изменения, внесенные в информационную базу после начала транзакции, будут затем либо целиком записаны, либо целиком отменены. Ключевыми функциями для работы в транзакции всегда являлись: начать, завершить, отменить. Вспомогательные: активность другой транзакции, возможность начала транзакции.
Простой пример использования транзакций приведен в коде ниже, который устанавливает зависимость создания элемента справочника номенклатуры и элемента справочника единицы измерения, владельцем которого является номенклатура. Если не удалось создать базовую единицу измерения, то и нельзя сохранять саму новую номенклатуру. Все действия происходят в транзакции:
Код 1C v 8.х
Попытка
// Начать транзакцию
НачатьТранзакцию( РежимУправленияБлокировкойДанных.Автоматический );
// Создать элемент номенклатура, записать
// Создать элемент единицы измерения, установить владельца, записать
// Обновить базовую единицу измерения в номенклатуре, записать
// Зафиксировать транзакцию
ЗафиксироватьТранзакцию();
Исключение
// Отменить транзакцию
ОтменитьТранзакцию();
КонецПопытки;
Что такое транзакция записи документа в 1с
У объектов есть событие ПриЗаписи. И выполняется оно уже в транзакции.
Для чего это делать в транзакции, какие выгоды мы получаем от этого?
(0) Когда движения документа записываются?
я опечатался
Имел ввиду перед записью
очевидно, чтобы можно было откатить все сделанные изменения. Не?
(3) а если транзакцию начать после ПередЗаписью, разве изменения не откатятся?
(4) В таком случае изменения, которые сделал в ПередЗаписью не откатятся.
(5) об изменениях какого рода ты говоришь? приведи пример кода
(6) перед записью документа изменяешь какой-либо реквизит справочника, записываешь. ПриЗаписи произошла ошибка, изменения откатываются включая справочник
(6)Например проверояешь правильность заполнения документа и если чтото не сошлось со звездами — шлешь пользователя в сад и Отказ=Истина.
(7) если ты хочешь делать такие изменения то их нужно делать ПриЗаписи
(8) но что должно откатиться с помощью транзакции? там про это речь была
ты не написал про это
(9)А я хочу перед записью.Что мне помешает?
(6) обычно перед записью какие-то реквизиты меняем. Например СуммаДокумента всегда считается в типовых.
(10)Все. Документ просто не будет записан. И останется в редактируемом состоянии.или просто не запишется если програмно пишешь
(12) в таком случае ты меняешь реквизиты у объекта, который находится в памяти, а не в БД
транзакция откатывает изменения в БД
(9) Смешно. это ты мощно протупил. ПриЗаписи уже всё записано, вся фишка в том, что ПриЗаписи вызывается фактически уже после записи. Непонятно, почему-то назвали ПриЗаписи.
(15)Если попростому останется то, что лежит в .Ссылка.
(15)Сам попробуй сначала, а потом будешь писать уствердительные предлоэжения
(18) а что именно пробовать? я знаю что следующий код не меняет БД:
(19)Ну и что? Ты в документе поменяй реквизиты, они тоже пока не записаны в БД. Если будет отказ в ПередЗаписью, то и не будут записаны. Транзакция записи откатится, .Ссылка останется такой же как до записи
(20) что именно ты предлагаешь мне попробовать сделать в (18) того что я не сделал в (19)
?
(21)Ну и в чем вопрос тогда?
воду варишь.
(23) ответь на вопрос
(24)Сформулиру его
(24) мне тоже интересно, что же он ответит )
(25) вопрос в (21)
Транзакция откатывает изменения сделанные в базе данных (запись, удаление итд)
когда я меняю реквизит объекта, как в (19) то я не меняю базу данных, ничего не записываю, не удаляю
так зачем здесь нужна транзация?
Автор, видимо, хочет до записи объекта в БД создавать связанные объекты с самостоятельной обработкой исключений и самостоятельным принятием решения о том, комиттить или откатить.
Если бы ПередЗаписью выполнялось не в транзакции, то это было бы возможно.
А сейчас любое исключение (перехваченное кодом) приведет к последующему откату транзакции записи.
(27)Транзакция нужна для возможности отказа от записи.А что ты делаешь с реквитами -это твое дело, твой реквизит никуда не денется, просто не запишется в базу.
+(29)Но на форме он останется
ПередЗаписью — ты можешь изменить реквизиты объекта и они запишутся в базу
ПриЗаписи — ты можешь изменить реквизиты объекта, но они уже не запишутся в базу, так как запись уже прошла.
(31 зачем создавать связанные объекты ПередЗаписью если это можно сделать при записи?
(29) отказаться от записи можно и до транзакции
+(32) точнее так: зачем создавать связанные объекты если ты еще не определился, будешь ли ты записывать текущий объект
(33)Интересно, если ты объект создал программно, где ты собираешься отказываться от записи?
(34)Перед записью можно сравнить версию в базе с текущей версией. При фиксации версионирования без транзакции перед записью очень грустно было бы.
(32) например: создать новый элемент справочника и присвоить ссылку реквизиту документа, как это сделать в ПриЗаписи?
Для того, чтобы объект в памяти сохранил свою целостность.
Например, в перед записью ты изменяешь какой-то реквизит, а следующей строкой ты пишешь отказ, так вот чтобы реквизит остался в состоянии как был «перед записью».
(32) В ПриЗаписи ты меняешь реквизит только в памяти. В базу он не попадет. Так и останется в оперативной памяти.
(32) Кто говорил про связанные объекты? Связанные объекты ПередЗаписью создавать, теоретически можно, но с плясками, так как ссылки на основной в этот момент ещё нет.
Разговор (у меня) шел об изменениях реквизитов этого самого объекта.
(35) в смысле где?
Предположим есть обработчик ПередЗаписью, транзакция еще не началась, и ты там выставляешь Отказ=Истина (до транзакции). Не вижу здесь проблем. К слову, именно так сейчас работает обработчик ПередЗаписью у формы. Он находится на клиенте, транзакция там еще не началась и запись можно прервать.
(38) да, это интересное полезное поведение, но это не результат работы транзакции
(37) с этим согласен
(36) если бы в ПередЗаписью не было транзакции то, не думаю, что были бы сложности с версионированием
сравнить можно было бы ПередЗаписью, а записывать в ПриЗаписи
(44) Устройся работать в 1С в раздел разработки платформы, реализуй хотелку и уволься.
(45) хотелка? ты шутишь, старичок?!
ветку я создал чтобы разобраться
Меня больше волнует почему нет события ПослеЗаписи в модуле объекта.
По сабжу, почему так сделано я не знаю. Определенная логика в этом есть. Это среда разработки упрощенная, создана так специально для низкого входного порога студентов. Миллион событий ПередТранзакциейЗаписи, ВСамомНачалеТранзакцииЗаписи и так далее посчитали ненужным.
Если очень надо что-то ПередЗаписью сделать вне транзакции, то фоновые задания помогут. Хотя метод извращенный и по производительности ресурсоемкий, но однажды я столкнулся, что по-другому никак.
Когда результаты проверок на разрешение записи и проведения надо было записывать для дальнейшего анализа. Просто ПередЗаписью куда-то там писать нельзя, т.к. если запись не состоится, то транзакция откатится. Внешнюю базу использовать не захотелось.
(46) Я серьезно, сынуля.
(47) зачем тебе ПослеЗаписи?
(49) Выполнить синхронизацию данных текущего объекта с остальными объектами БД, которой не хочется нагружать транзакцию самой записи объекта.
(50) а если ошибка случится? как здесь без транзакции?
(51) Ночная регламентная процедура ошибки синхронизации найдет и исправит. Синхронизации действительно критичные для реал-тайма вне транзакции не делаются.
Ты не переживай, я далеко не зеленый новичок, все последствия представляю и все будет предусмотрено 🙂
Транзакция записи хорошее дело, но ее завершения должен дождаться пользователь, а там бывает надолго.
(52) да ты не переживай, что я переживаю)
у тебя дублирование операции получается, не видно в необходимости ПослеЗаписи
(53) Дублирования операций нет, есть вынос части операции вне основной транзакции записи. Нужно для того, чтобы пользователь не ждал завершения этой синхронизации.
Так как, во-первых, она может быть продолжительной по времени (ожидание блокировки, например), а пользователю надо работать дальше, для его текущей деятельности эта синхронизация не так важна.
А во-вторых, если действительно произойдет ошибка, то не надо запрещать пользователю запись и проведение. Эту ошибку исправит потом автоматический регламент или с ней будет разбираться поддержка, получив оповещение от системы о найденных, но не исправленных рассинхронах.