Заголовок вышел броским, но накипело. Сразу скажу, что речь пойдет об 1С. Дорогие 1С-ники, вы не умеете работать с транзакциями и не понимаете что такое исключения. К такому выводу я пришел, просматривая большое количество кода на 1С, рождаемого в дебрях отечественного энтерпрайза. В типовых конфигурациях с этим все достаточно хорошо, но ужасающее количество заказного кода написано некомпетентно с точки зрения работы с базой данных. Вы когда-нибудь видели у себя ошибку "В данной транзакции уже происходили ошибки"? Если да — то заголовок статьи относится и к вам. Давайте под катом разберемся, наконец, что такое транзакции и как правильно с ними обращаться, работая с 1С.


Почему надо бить тревогу


Для начала, давайте разберемся, что же такое представляет собой ошибка "В данной транзакции уже происходили ошибки". Это, на самом деле, предельно простая штука: вы пытаетесь работать с базой данных внутри уже откаченной (отмененной) транзакции. Например, где-то был вызван метод ОтменитьТранзакцию, а вы пытаетесь ее зафиксировать.


Почему это плохо? Потому что данная ошибка ничего не говорит вам о том, где на самом деле случилась проблема. Когда в саппорт от пользователя приходит скриншот с таким текстом, а в особенности для серверного кода, с которым интерактивно не работает человек — это… Хотел написать "критичная ошибка", но подумал, что это buzzword, на который уже никто не обращает внимания…. Это задница. Это ошибка программирования. Это не случайный сбой. Это косяк, который надо немедленно переделывать. Потому что, когда у вас фоновые процессы сервера встанут ночью и компания начнет стремительно терять деньги, то "В данной транзакции уже происходили ошибки" это последнее, что вы захотите увидеть в диагностических логах.


Есть, конечно, вероятность, что технологический журнал сервера (он ведь у вас включен в продакшене, да?) как-то поможет диагностировать проблему, но я сейчас навскидку не могу придумать вариант — как именно в нем найти реальную причину указанной ошибки. А реальная причина одна — программист Вася получил исключение внутри транзакции и решил, что один раз — не карабас "подумаешь, ошибка, пойдем дальше".


Что такое транзакции в 1С


Неловко писать про азбучные истины, но, видимо, немножго придется. Транзакции в 1С — это то же самое, что транзакции в СУБД. Это не какие-то особенные "1С-ные" транзакции, это и есть транзакции в СУБД. Согласно общей идее транзакций, они могут либо выполниться целиком, либо не выполниться совсем. Все изменения в таблицах базы данных, выполненные внутри транзакции, могут быть разом отменены, как будто ничего не было.


Далее, нужно понимать, что в 1С не поддерживаются вложенные транзакции. Собственно говоря, они не поддерживаются не "в 1С", а вообще не поддерживаются. По-крайней мере, теми СУБД, с которыми умеет работать 1С. Вложенных транзакций, например, нет в MS SQL и Postgres. Каждый "вложенный" вызов НачатьТранзакцию просто увеличивает счетчик транзакций, а каждый вызов "ЗафиксироватьТранзакцию" — уменьшает этот счетчик. Данное поведение описано в множестве книжек и статей, но выводы из этого поведения, видимо, разобраны недостаточно. Строго говоря, в SQL есть т.н. SAVEPOINT, но 1С их не использует, да и вещь это достаточно специфичная.


Здесь и далее, специально для Воинов Истинной Веры, считающих, что код должен писаться только на английском, под спойлерами будет приведен аналог кода в англоязычном синтаксисе 1С.


Процедура ОченьПолезныйИВажныйКод(СписокСсылокСправочника)

    НачатьТранзакцию();

    Для Каждого Ссылка Из СписокСсылокСправочника Цикл
        ОбъектСправочника = Ссылка.ПолучитьОбъект();
        ОбъектСправочника.КакоеТоПоле = "Я изменен из программного кода";
        ОбъектСправочника.Записать();
    КонецЦикла;

    ЗафиксироватьТранзакцию();

КонецПроцедуры

Код на английском

На самом деле, нет. Мне совершенно не хочется дублировать примеры на английском только ради того, чтобы потешить любителей холиваров и священных войн.


Вы же наверняка пишете такой код, да? Приведенный пример кода содержит ошибки. Как минимум, три. Знаете какие? Про первую я скажу сразу, она связана с объектными блокировками и не имеет отношения непосредственно к транзакциям. Про вторую — чуть позже. Третья ошибка — это deadlock, который возникнет при параллельном исполнении этого кода, но это тема для отдельной статьи, ее рассматривать сейчас не будем, дабы не усложнять код. Ключевое слово для гугления: deadlock управляемые блокировки.


Обратите внимание, простой ведь код. Такого в ваших 1С-системах просто вагон. И он содержит сразу, как минимум, 3 ошибки. Задумайтесь на досуге, сколько ошибок есть в более сложных сценариях работы с транзакциями, написанных вашими программистами 1С :)


Объектные блокировки


Итак, первая ошибка. В 1С существуют объектные блокировки, так называемые "оптимистические" и "пессимистические". Кто придумал термин, не знаю, убил бы :). Совершенно невозможно запомнить, какая из них за что отвечает. Подробно про них написано здесь и здесь, а также в прочей IT-литературе общего назначения.


Суть проблемы в том, что в указанном примере кода изменяется объект базы данных, но в другом сеансе может сидеть интерактивный пользователь (или соседний фоновый поток), который тоже будет менять этот объект. Здесь один из вас может получить ошибку "запись была изменена или удалена". Если это произойдет в интерактивном сеансе, то пользователь почешет репу, ругнется и попробует переоткрыть форму. Если это произойдет в фоновом потоке, то вам придется искать это в логах. А журнал регистрации, как вы знаете, медленный, а ELK-стек для журналов 1С у нас в отрасли настраивают единицы… (мы, к слову, входим в число тех, кто настраивает и другим помогает настраивать :))


Короче говоря, это досадная ошибка и лучше, чтобы ее не было. Поэтому, в стандартах разработки четко написано, что перед изменением объектов необходимо ставить на них объектную блокировку методом "ОбъектСправочника.Заблокировать()". Тогда параллельный сеанс (который тоже должен так поступить) не сможет начать операцию изменения и получит ожидаемый, управляемый отказ.


А теперь про транзакции


С первой ошибкой разобрались, давайте перейдем ко второй.


Если не предусмотреть проверку исключения в этом методе, то исключение (например, весьма вероятное на методе "Записать()") выбросит вас из данного метода без завершения транзакции. Исключение из метода "Записать" может быть выброшено по самым разным причинам, например, сработают какие-то прикладные проверки в бизнес-логике, или возникнет упомянутая выше объектная блокировка. Так или иначе, вторая ошибка гласит: код, начавший транзакцию, не несет ответственность за ее завершение.



Именно так я бы назвал эту проблему. В нашем статическом анализаторе кода 1С на базе SonarQube мы даже отдельно встроили такую диагностику. Сейчас я работаю над ее развитием, и фантазия программистов 1С, чей код попадает ко мне на анализ, порой приводит меня в шок и трепет…


Почему? Потому что выброшенное наверх исключение внутри транзакции в 90% случаев не даст эту транзакцию зафиксировать и приведет к ошибке. Следует понимать, что 1С автоматически откатывает незавершенную транзакцию только после возвращения из скриптового кода на уровень кода платформы. До тех пор, пока вы находитесь на уровне кода 1С, транзакция остается активной.


Поднимемся на уровень выше по стеку вызовов:


Процедура ВажныйКод()

     СписокСсылок = ПолучитьГдеТоСписокСсылок();
     ОченьПолезныйИВажныйКод(СписокСсылок);

КонецПроцедуры

Смотрите, что получается. Наш проблемный метод вызывается откуда-то извне, выше по стеку. На уровне этого метода разработчик понятия не имеет — будут ли какие-то транзакции внутри метода ОченьПолезныйИВажныйКод или их не будет. А если будут — то будут ли они все завершены… Мы же все тут за мир и инкапсуляцию, верно? Автор метода "ВажныйКод" не должен думать про то, что именно происходит внутри вызываемого им метода. Того самого, в котором некорректно обрабатывается транзакция. В итоге, попытка поработать с базой данных после выброса исключения изнутри транзакции, с высокой вероятностью приведет к тому, что "В данной транзакции бла-бла…"


Размазывание транзакций по методам


Второе правило "транзакционно-безопасного" кода: счетчик ссылок транзакций в начале метода и в его конце должен иметь одно и то же значение. Нельзя начинать транзакцию в одном методе и завершать ее в другом. Из этого правила, наверное, можно найти исключения, но это будет какой-то низкоуровневый код, который пишут более компетентные люди. В общем случае так писать нельзя.


Например:


Процедура ВажныйКод()

     СписокСсылок = ПолучитьГдеТоСписокСсылок();
     ОченьПолезныйИВажныйКод(СписокСсылок);

     ЗафиксироватьТранзакцию();

     // Путевка в ад, серьезный разговор с автором о наших сложных трудовых отношениях.

КонецПроцедуры

Выше — неприемлемый говнокод. Нельзя писать методы так, чтобы вызывающая сторона помнила и следила за возможными (или вероятными — как знать) транзакциями внутри других методов, которые она вызывает. Это нарушение инкапсуляции и разрастание спагетти-кода, который невозможно трассировать, сохраняя рассудок.


Особенно весело вспомнить, что реальный код намного больше синтетических примеров из 3-х строчек. Выискивать начинающиеся и завершающиеся транзакции по шести уровням вложенности — это прям мотивирует на задушевные беседы с авторами.


Пытаемся исправить код


Вернемся к исходному методу и попытаемся его починить. Сразу скажу, что объектную блокировку мы чинить пока не будем, просто, чтобы не усложнять код примера.


Первый подход типичного 1С-ника


Обычно программисты 1С знают, что при записи может быть выдано исключение. А еще они боятся исключений, поэтому стараются их все перехватывать. Например, вот так:


Процедура ОченьПолезныйИВажныйКод(СписокСсылокСправочника)

    НачатьТранзакцию();

    Для Каждого Ссылка Из СписокСсылокСправочника Цикл
        ОбъектСправочника = Ссылка.ПолучитьОбъект();

        ОбъектСправочника.КакоеТоПоле = "Я изменен из программного кода";
        Попытка
              ОбъектСправочника.Записать();
        Исключение
              Лог.Ошибка("Не удалось записать элемент %1", Ссылка);
              Продолжить;
        КонецПопытки;
    КонецЦикла;

    ЗафиксироватьТранзакцию();

КонецПроцедуры

Ну как, стало лучше, да? Ведь теперь, возможные ошибки записи обрабатываются и даже логируются. Исключения больше не возникнут при записи объекта. И в логе даже видно — на каком объекте, не поленился, вывел в сообщение ссылку вместо лаконичного "Ошибка записи справочника", как это часто любят писать вечно торопящиеся разработчики. Иными словами, налицо забота о пользователе и рост компетенций.


Однако, опытный 1С-ник здесь скажет, что нет, лучше не стало. По сути ничего не поменялось, а может даже стало и хуже. В методе "Записать()" платформа 1С сама начнет транзакцию записи, и эта транзакция будет уже вложенной по отношению к нашей. И если в момент работы с базой данных 1С свою транзакцию откатит (например, будет выдано исключение бизнес-логики), то наша транзакция верхнего уровня все равно будет помечена как "испорченная" и ее нельзя будет зафиксировать. В итоге этот код так и останется проблемным, и при попытке фиксации выдаст "уже происходили ошибки".


А теперь представьте, что речь идет не о маленьком методе, а о глубоком стеке вызовов, где в самом низу кто-то взял и "выпустил" начатую транзакцию из своего метода. Верхнеуровневые процедуры могут и понятия не иметь, что кто-то там внизу начинал транзакции. В итоге, весь код валится с невнятной ошибкой, которую расследовать невозможно в принципе.


Код, который начинает транзакцию, обязан завершить или откатить ее. Не взирая ни на какие исключения. Каждая ветка кода должна быть исследована на предмет выхода из метода без фиксации или отмены транзакции.


Методы работы с транзакциями в 1С


Не будет лишним напомнить, что вообще 1С предоставляет нам для работы с транзакциями. Это всем известные методы:


  • НачатьТранзакцию()
  • ЗафиксироватьТранзакцию()
  • ОтменитьТранзакцию()
  • ТранзакцияАктивна()

Первые 3 метода очевидны и делают то, что написано в их названии. Последний метод — возвращает Истину, если счетчик транзакций больше нуля.


И есть интересная особенность. Методы выхода из транзакции (Зафиксировать и Отменить) выбрасывают исключения, если счетчик транзакций равен нулю. То есть, если вызвать один из них вне транзакции, то возникнет ошибка.


Как правильно пользоваться этими методами? Очень просто: надо прочитать сформулированное выше правило: код, начавший транзакцию, должен нести ответственность за ее завершение.


Как же соблюсти это правило? Давайте попробуем:


НачатьТранзакцию();
ДелаемЧтоТо();
ЗафиксироватьТранзакцию();

Выше мы уже поняли, что метод ДелаемЧтоТо — потенциально опасен. Он может выдать какое-то исключение, и транзакция "вылезет" наружу из нашего метода. Окей, добавим обработчик возможного исключения:


НачатьТранзакцию();
Попытка
    ДелаемЧтоТо();
Исключение
    // а что же написать тут?
КонецПопытки;
ЗафиксироватьТранзакцию();

Отлично, мы поймали возникающую ошибку, но что с ней делать? Записать сообщение в лог? Ну, может быть, если код логирования ошибок должен быть именно на этом уровне и ошибку мы тут ждем. А если нет? Если мы не ожидали тут никаких ошибок? Тогда мы должны просто передать это исключение выше, пусть с ними разбирается другой слой архитектуры. Делается это оператором "ВызватьИсключение" без аргументов. В этих ваших джава-сиплюсплюсах это делается точно так же оператором throw.


НачатьТранзакцию();
Попытка
    ДелаемЧтоТо();
Исключение
    ВызватьИсключение;
КонецПопытки;
ЗафиксироватьТранзакцию();

Так, стоп… Если мы просто прокидываем исключение дальше, то зачем тут вообще нужна Попытка? А вот зачем: правило заставляет нас обеспечить завершение начатой нами транзакции.


НачатьТранзакцию();
Попытка
    ДелаемЧтоТо();
Исключение
    ОтменитьТранзакцию();
    ВызватьИсключение;
КонецПопытки;
ЗафиксироватьТранзакцию();

Теперь, вроде бы, красиво. Однако, мы ведь помним, что не доверяем коду ДелаемЧтоТо(). Вдруг там внутри его автор не читал этой статьи, и не умеет работать с транзакциями? Вдруг он там взял, да и вызвал метод ОтменитьТранзакцию или наоборот, зафиксировал ее? Нам очень важно, чтобы обработчик исключения не породил нового исключения, иначе исходная ошибка будет потеряна и расследование проблем станет невозможным. А мы помним, что методы Зафиксировать и Отменить могут выдать исключение, если транзакция не существует. Здесь-то и пригождается метод ТранзакцияАктивна.


Финальный вариант


Наконец, мы можем написать правильный, "транзакционно-безопасный" вариант кода. Вот он:


**UPD: в комментариях предложен более безопасный вариант, когда ЗафиксироватьТранзакцию расположен внутри блока Попытка. Здесь приведен именно этот вариант, ранее Фиксация располагалась после блока Попытка-Исключение.


НачатьТранзакцию();
Попытка
    ДелаемЧтоТо();
    ЗафиксироватьТранзакцию();
Исключение
    Если ТранзакцияАктивна() Тогда
        ОтменитьТранзакцию();
    КонецЕсли;
    ВызватьИсключение;
КонецПопытки;

Постойте, но ведь не только "ОтменитьТранзакцию" может выдавать ошибки. Почему же тогда "ЗафиксироватьТранзакцию" не обернут в такое же условие с "ТранзакцияАктивна"? Опять же, по тому же самому правилу: код, начавший транзакцию, должен нести ответственность за ее завершение. Наша транзакция необязательно самая первая, она может быть вложенной. На нашем уровне абстракции мы обязаны заботиться только о нашей транзакции. Все прочие должны быть нам неинтересны. Они чужие, мы не должны нести за них ответственность. Именно НЕ ДОЛЖНЫ. Нельзя предпринимать попыток выяснения реального уровня счетчика транзакций. Это опять нарушит инкапсуляцию и приведет к "размазыванию" логики управления транзакциями. Мы проверили активность только в обработчике исключения и только для того, чтобы убедиться, что наш обработчик не породит нового исключения, "прячущего" старое.


Чек-лист рефакторинга


Давайте рассмотрим несколько наиболее распространенных ситуаций, требующих вмешательства в код.


Паттерн:


НачатьТранзакцию();
ДелаемЧтоТо();
ЗафиксироватьТранзакцию();

Обернуть в "безопасную" конструкцию с Попыткой, Проверкой активности и пробросом исключения.


Паттерн:


Если Не ТранзакцияАктивна() Тогда
    НачатьТранзакцию()
КонецЕсли

Анализ и Рефакторинг. Автор не понимал, что делает. Начинать вложенные транзакции можно безопасно. Не нужно проверять условие, нужно просто начать вложенную транзакцию. Ниже по модулю он наверняка еще там извращается с их фиксацией. Это гарантированный геморрой.


Примерно похожий вариант:


Если ТранзакцияАктивна() Тогда
    ЗафиксироватьТранзакцию()
КонецЕсли

аналогично: фиксация транзакции по условию — это странно. Почему тут условие? Что, кто-то иной мог уже зафиксировать эту транзакцию? Повод для разбирательства.


Паттерн:


НачатьТранзакцию()
Пока Выборка.Следующий() Цикл

    // чтение объекта по ссылке
    // запись объекта

КонецЦикла;
ЗафиксироватьТранзакцию();

  1. ввести управляемую блокировку во избежание deadlock
  2. ввести вызов метода Заблокировать
  3. обернуть в "попытку", как показано выше

Паттерн:


НачатьТранзакцию()
Пока Выборка.Следующий() Цикл

    Попытка
    Объект.Записать();
    Исключение
           Сообщить("Не получилось записать");
    КонецПопытки;

КонецЦикла;
ЗафиксироватьТранзакцию();

Данная транзакция уже не завершится в случае исключения. Нет смысла продолжать цикл. Код требуется переписать, сверяясь с исходной задачей. Дополнительно обеспечить более информативное сообщение об ошибке.


В заключение


Я, как вы уже, наверное, догадались, отношусь к людям, любящим платформу 1С и разработку на ней. К платформе, разумеется, есть претензии, особенно в среде Highload, но в общем и целом, она позволяет недорого и быстро разрабатывать очень качественные корпоративные приложения. Давая из коробки и ORM, и GUI, и веб-интерфейс, и Reporting, и много чего еще. В комментариях на Хабре обычно пишут всякое высокомерное, так вот, ребята — основная проблема 1С, как экосистемы — это не платформа и не вендор. Это слишком низкий порог вхождения, который позволяет попадать в отрасль людям, не понимающим, что такое компьютер, база данных, клиент-сервер, сеть и всякое такое. 1С сделала разработку корпоративных приложений слишком легкой. Я за 20 минут могу написать на ней учетную систему для закупок/продаж с гибкими отчетами и веб-клиентом. После этого, мне несложно подумать о себе, что и на больших масштабах можно писать примерно так же. Как-то там 1С сама все внутри сделает, не знаю как, но наверное сделает. Напишу-ка я "НачатьТранзакцию()"....


И знаете — самое главное, что это прекрасно. Простота разработки в 1С позволяет моментально реализовывать бизнес-идеи и встраивать их в процессы компании. Потом всегда можно отрефакторить, главное понимать как. И если вдруг вам нужна помощь в аудите вашей "медленной 1С" — обращайтесь к специалистам по оптимизации. Она совсем не медленная.

Комментарии (198)


  1. mayorovp
    13.08.2018 12:10
    +1

    В 1С существуют объектные блокировки, так называемые "оптимистические" и "пессимистические". Кто придумал термин, не знаю, убил бы :). Совершенно невозможно запомнить, какая из них за что отвечает.

    Так все очень просто же. Пессимистическая блокировка ускоряет худший случай, когда объект уже кем-то занят. Оптимистическая же ускоряет лучший случай, когда объект свободен.


    И эти термины придуманы вовсе не в 1C.


    1. EvilBeaver Автор
      13.08.2018 12:13

      Я знаю, что они придуманы не в 1С, и даже написал там про «литературу общего назначения». Однако, термины неочевидные (по крайней мере, мне лично). Что такое, например, «ускоряет худший случай»?


      1. mayorovp
        13.08.2018 12:19

        Худший случай — это случай когда объект уже кем-то занят. Для того, чтобы его ускорить, нужно обнаружить этот факт как можно раньше. Для этого и используется вызов Заблокировать(). При этом в лучшем случае, когда объект свободен, работа замедляется из-за лишнего вызова.


        Оптимистическая же блокировка построена так, чтобы в лучшем случае к базе не делался ни один лишний запрос. Ценой того что в худшем случае вся транзакция делалась впустую и будет откачена.


        1. EvilBeaver Автор
          13.08.2018 12:29

          Я постараюсь запомнить эту семантику, но есть риск, что опять запутаюсь :) В любом случае, спасибо!


          1. AsmodeusL
            13.08.2018 14:39
            +1

            Попробуйте использовать «ресторанную» аналогию.
            «Оптимист» просто поедет в заведение в надежде, что там окажутся свободные столики. «Пессимист» позвонит заранее и забронирует столик (либо узнает, что в данном заведении свободных столиков нет и не потратит время на пустую поездку).


            1. nlruslan
              13.08.2018 19:33
              +1

              Классная аналогия. Теперь точно запомнится. Спасибо!


  1. dmpas
    13.08.2018 12:23
    -1

        Если ТранзакцияАктивна() Тогда
            ОтменитьТранзакцию();
        КонецЕсли;
    

    Я обычно в случае отмены вместо Если делаю Пока. Кто его знает, что там в вызванной процедуре наоткрывалось.


    1. EvilBeaver Автор
      13.08.2018 12:29

      На мой взгляд, это все же перебор. В идеале — я должен отменить именно свою транзакцию, т.е. «вычесть» именно свою единичку, добавленную в счетчик транзакций.


      1. dmpas
        13.08.2018 12:33

        Ну, это если абстрактная процедура в вакууме. Какая-нибудь экспортная в общем модуле. Если список мест применения известен и ограничен, то отмена транзакции в вложенной процедуре не имеет смысла, а на внешнем контуре — полный откат циклом.


    1. exit999
      13.08.2018 18:37

      Вот как раз писать «Пока ТранзакцияАктивна() Цикл» нельзя, потому что вы можете закрыть чужую внешнюю транзакцию, внутри которой вызывался ваш код. Отменять эту внешнюю транзакцию ваш код не должен, так как внешний код сам должен обрабатывать свои транзакции.


      1. dmpas
        13.08.2018 18:50

        Согласен. Я выше уже уточнил, что это стоит делать на внешнем контуре транзакции.


  1. krovlads
    13.08.2018 15:30

    Андрей Овсянкин — Троль 80 левела


    1. nlruslan
      13.08.2018 16:53

      В чем заключается троллинг?


      1. krovlads
        13.08.2018 17:06

        Посмотрите под спойлером «Код на английском»


        1. nlruslan
          13.08.2018 18:40

          А вот вы о чем. Просите, я Вас не правильно понял)


          1. fishca
            14.08.2018 09:41

            Я кстати повелся :)


  1. servekon
    13.08.2018 16:32

    Можете пояснить зачем вообще напрямую работать с транзакциями? Обычно пишут код на вставку/обновление/удаления данных из базы данных, а движок базы данных уже сам решает где, когда и в каком порядке проводить транзакцию. В 1С нет механизма работы с базой данных без транзакций?


    1. EvilBeaver Автор
      13.08.2018 16:53
      +4

      В 1С нет механизма работы с базой данных без транзакций?

      А что, где-то можно работать с базой данных без транзакций?


      1. fishca
        13.08.2018 17:01

        Скорее всего имелось ввиду: явная и неявная транзакции…


      1. servekon
        13.08.2018 17:20

        Можно ли писать вместо

        НачатьТранзакцию()
        Пока Выборка.Следующий() Цикл
        
            Попытка
            Объект.Записать();
            Исключение
                   Сообщить("Не получилось записать");
            КонецПопытки;
        
        КонецЦикла;
        ЗафиксироватьТранзакцию();


        Что-то типа этого:
        
        Запрос."Обновить 'таблицу' сделать 'поле1' = 'значение1' где 'ид записи' = 1"
        


        1. nixel
          13.08.2018 17:35

          нет, нельзя. но можно и не открывать явно транзакцию, при каждой записи объекта она будет открываться и закрываться неявно.
          другой вопрос, что часто требуется согласованно менять данные в нескольких таблицах, и это без явного управления транзакциями сделать невозможно.

          p.s. возможно, конечно, на несвязанных неявных транзакциях, но в случае эксепшена вы сами себе злобный буратино.


        1. EvilBeaver Автор
          13.08.2018 17:37
          +1

          UPDATE/DELETE и прочие DDL инструкции в запросах 1С запрещены, т.к. используется подход «бизнес-объектов», а не записей в таблицах БД. Каждое изменение данных сопровождается (может сопровождаться) каким-то прикладным кодом, проверками по бизнес-логике, etc. Поэтому, прямая модификация таблиц от разработчика закрыта. Только через слой бизнес-логики объекта.


      1. GreatDit
        15.08.2018 10:56
        -1

        да где угодно. Цитата от микрософт: «всегда можно переписать программу так, что транзакции будут не нужны».


        1. EvilBeaver Автор
          15.08.2018 10:56

          И что, MS SQL Server позволит выполнять операции с базой данных вне транзакций? Серьезно?


    1. vanxant
      14.08.2018 02:20
      +1

      Откройте для себя термин целостность базы данных.
      Ну, пример.
      Вы переводите деньги со счета на счет.
      С одного, допустим, успешно сняли.
      А вот при добавлении денег на второй счет по какой-то причине запись не прошла.
      Без явных транзакций деньги просто потеряются в вашей кривой программе. Да, в тот самый момент, когда код будет начислять вам зарплату:)


  1. Dream2
    13.08.2018 18:34

    Автор, я не претендую на абсолютную истину, но где-то читал, что верна следующая конструкция:

    Попытка
    НачатьТранзакцию();

    Исключение
    КонецПопытки;


    1. Laz_Artem
      13.08.2018 18:50

      НачатьТранзакцию();
      Попытка

      ЗафиксироватьТранзакцию();
      Исключение
      ОтменитьТранзакцию();
      ВызватьИсключение;
      КонецПопытки;


    1. EvilBeaver Автор
      13.08.2018 18:54
      +2

      Внутри блока Исключение нельзя оставлять пустоту. Полный игнор ошибок это как ездить ночью без фар по встречке, да еще и предварительно слив тормозуху.


  1. Laz_Artem
    13.08.2018 18:34

    Автор не упомянул, что только после не восстановимого исключения мы можем получить ошибку «В данной транзакции уже происходили ошибки»
    (https://its.1c.ru/db/content/metod8dev/src/developers/platform/metod/other/i8102313.htm?_=1533744348)


    1. EvilBeaver Автор
      13.08.2018 18:35

      Автор просто не сказал, что мы в 1С не знаем — восстановимое или невосстановимое исключение мы поймали


      1. o4karek
        13.08.2018 20:48
        +1

        Невосстановимая ошибка не ловится. Если такая ошибка случилась — она вылетает из встроенного языка безо всяких Попытка Исключение, сразу в интерактив. А если исключение поймалось — оно восстановимое. Но от этого ошибка в транзакции все равно наступит.
        its.1c.ru/db/v83doc/bookmark/dev/TI000000528
        Ну и цитата из ссылки:

        В зависимости от характера произошедшей ошибки возможны различные сценарии обработки этой ситуации.
        Если произошедшая ошибка не связана с базой данных, то возможно продолжение транзакции и дальнейшей работы модуля. Если разработчик считает это необходимым, он может отменить транзакцию или, наоборот, продолжить выполнение транзакции, если произошедшая ошибка не нарушает атомарность транзакции.
        Если же исключительная ситуация была вызвана ошибкой базы данных, то система фиксирует факт возникновения ошибки в этой транзакции, и дальнейшее продолжение транзакции или ее фиксация становятся невозможны. Единственная операция с базой данных, которую разработчик может произвести в данной ситуации, ? это отмена транзакции. После этого он может осуществить попытку выполнения этой транзакции еще раз.


        1. EvilBeaver Автор
          13.08.2018 21:16

          Я как раз про то, что мы не знаем — с базой данных связана ошибка или нет. В отсутствие типизированных исключений сложно сказать — пометила платформа транзакцию, как ошибочную или нет.


          1. o4karek
            13.08.2018 21:32

            Тогда это не про [не]восстановимое исключение :)
            Причина исключения, действительно, не предоставляется


            1. EvilBeaver Автор
              13.08.2018 21:47

              Вопрос терминологии. С точки зрения транзакции — оно невосстановимое. Можно только откатиться. С точки зрения ВМ 1С — восстановимое, т.к. его можно поймать


              1. o4karek
                13.08.2018 22:32

                Есть подозрение, что лучше использовать ту терминологию, которая и в официальной документации. Просто для того, чтобы не было многозначных толкований простых фраз.
                И, насколько я понимаю, невосстановимость исключения заключается в том, что в некоторых случаях нельзя продолжить исполнение встроенного языка.


                1. EvilBeaver Автор
                  13.08.2018 23:17

                  Я не помню места в документации, в котором бы давалось определение невосстановимого исключения. Но я и не знаю ее наизусть, возможно, упустил. Покажете?


                  1. o4karek
                    13.08.2018 23:39

                    Ссылка выше дает такое определение.
                    Но, с формальной точки зрения, оно отличается от того, что я сказал выше :)


        1. Laz_Artem
          16.08.2018 09:42
          -1

          nixel, o4karek

          Невосстановимая ошибка не ловится

          не правда, у вас не верное понимание что есть восстановимая, а что нет.

          Берем для примера такой код
          	НачатьТранзакцию();
          	
          	ЗаписатьВБазу();
          	Попытка
          		а = 1 /0;
          	Исключение
          	КонецПопытки;
          	
          	ПрочитатьИзБазы();
          	
          	ЗафиксироватьТранзакцию();
          


          это есть восстановимое исключение т.к. ничего страшного не произойдет в этом случаи.
          Теперь меняем код на такой:
          	НачатьТранзакцию();
          	
          	Попытка
          		ЗаписатьВБазу();
                  Исключение
          	КонецПопытки;
          	
          	ПрочитатьИзБазы();
          
          	ЗафиксироватьТранзакцию();
          


          в модуль объекта при записи мы переносим эту строку
          а = 1 /0;
          Вот теперь при попытки прочитать из базы мы получим ту самую ошибку «В данной транзакции уже происходили ошибки»
          Вот это и есть невосстановимая ошибка!

          А вот если мы перенесем это в событие при записи
          Попытка
          а = 1 /0;
          Исключение
          КонецПопытки;

          ошибки «В данной транзакции уже происходили ошибки» не произойдет. Так что понятие «восстановимое/не восстановимое исключение» это довольно таки тонкая грань которую нельзя формализировать.


          1. o4karek
            16.08.2018 09:54

            :)
            В данном случае я просто постулирую, что я прав, а вы — нет :)
            Причин много, но одна вот:

            Невосстановимые ошибки ? это ошибки, при возникновении которых нормальное функционирование системы «1С: Предприятие» может быть нарушено, например, могут быть испорчены данные. При возникновении невосстановимой ошибки выполнение системы «1С: Предприятие» прекращается в любом случае.

            Это цитата из платформенной документации. Именно это фирма «1С» считает невосстановимой ошибкой. При этом совершенно безразлично, где эта ошибка случится — в транзакции или вне транзакции. Результат будет один — при возникновении невосстановимой ошибки ваш код полностью прекратит свою работу. При этом прекратит работу также и клиентское приложение, а также — может умереть и сервер.
            А дальше опять читаем цитату из поста habr.com/post/419715/#comment_18990099 или или по ссылке из того же поста читаем весь раздел документации.
            Не надо придумывать, что фирма «1С» понимает под какими-то терминами, особенно если сама фирма «1С» привела в своей документации прямые определения :)


            1. Laz_Artem
              16.08.2018 10:03

              Не нужно приводить всякого рода выдержки из документации, просто проверьте. Смысл не в том какое определение дано восстановимому или не восстановимому исключению, смысл в том как это все работает. И основной посыл EvilBeaver (как мне кажется) был в том, что бы обратить на это внимание и возможно научить кого-то. А вы тут про терминологию.


              1. o4karek
                16.08.2018 10:21
                +2

                Любой спор — это спор об определениях. Если использовать неверные определения — спор бесполезный.
                Просто поверьте мне безотносительно — невосстановимое исключение вы во встроенном языке не поймаете (т.е. это аксиома). Соответственно: все остальные исключения — восстановимые. И вот как восстановимое исключение обрабатывается платформой при использовании транзакций — это и есть тема обсуждения текущей статьи.
                И чтобы обсуждать вопросы с единым использованием применяемых терминов — лучше использовать терминологию вендора, а не придуманную самостоятельно.


    1. nixel
      13.08.2018 18:39
      +1

      По вашей же ссылке во втором абзаце написано обратное. Данная ошибка может возникать при восстановимом исключении на работе с БД. Причем не обязательно на записи, исключение при чтении тоже выкинет "в данной транзакции уже происходили ощибки" при повторном обращении к бд


  1. Dream2
    13.08.2018 18:42
    -1

    Ошибся в первом сообщении, вот полная конструкция:

    Попытка
    НачатьТранзакцию();
    Действия();
    ЗафиксироватьТранзакцию();
    Исключение
    КонецПопытки;

    Почему именно так: все, что находится внутри Попытки, убивается в случае ошибок в Исключении, т.е. нет нужды отменять транзакцию, она отменится автоматически. Фиксация транзакции расположена последней в очереди внутри попытки. По-моему, вопрос закрыт. Как вы считаете?


    1. nixel
      13.08.2018 18:49
      +1

      Она отменится автоматически только в конце серверного вызова, либо когда кто-то выше по стеку не сделает отмену либо фиксацию. Без явной отмены в блоке попытки весь код выше по стеку, выполняемый после вызова данного куска с попыткой будет в открытой поломанной транзакции


      1. Dream2
        13.08.2018 18:58

        Я пробовал отменять в блоке попытки, вылезала ошибка: «Транзакция не активна»


        1. EvilBeaver Автор
          13.08.2018 19:06

          Значит она неактивна. Перечитайте статью еще раз


          1. Dream2
            13.08.2018 19:08

            А неактивна она, потому что Исключение отменяет транзакцию )


            1. EvilBeaver Автор
              13.08.2018 19:22

              Нет.


          1. Dream2
            13.08.2018 19:11

            Можно еще вот так, но я не вижу в этом смысла:

            Попытка
            НачатьТранзакцию();
            Действия();
            ЗафиксироватьТранзакцию();
            Исключение
            Если ТранзакцияАктивна() Тогда
            ОтменитьТранзакцию();
            Конецесли;
            КонецПопытки;


    1. EvilBeaver Автор
      13.08.2018 18:52

      А оставлять пустым блок Исключение — это вообще преступление


  1. Dream2
    13.08.2018 19:42
    -1

    1С. Ошибка «В данной транзакции уже происходили ошибки».
    Причина появления данной ошибки — вызов исключительной ситуации во вложенной транзакции. Попытка создает неявную транзакцию, тем самым исключение откатывает транзакцию полностью.

    Внутри вложенной транзакции вызывается исключительная ситуация, что откатывает всю транзакцию и некорректно отрабатывает. Например:

    НачатьТранзакцию();


    Попытка

    Исключение // если тут будет вызвано исключение — то вы увидите ошибку «В данной транзакции уже происходили ошибки»

    КонецПопытки;

    КонецТранзакцию();
    Во избежания таких ситуаций, нужно избегать использования попытки внутри транзакции.

    avditor.ru/index.php/programmirovanie-1s/102-1s-oshibka-v-dannoj-tranzaktsii-uzhe-proiskhodili-oshibki


    1. mayorovp
      13.08.2018 21:02

      Ну и чем написанное вами отличается от написанного в статье?


    1. Dementor
      13.08.2018 22:28
      +1

      Вы запутались. Еще раз по полочкам:
      1) Если внутри открытой транзакции происходит ошибка, то происходит откат ранее записанного и прерывание выполнения (без использования попыток мы вообще не попадем на «В данной транзакции уже происходили ошибки»)

      2) Не очень знаком с неявными транзакциями «попыток» (они явно не полноценные, так как если в попытке сделать запись нескольких элементов и на одном из них получить ошибку, то предыдущие останутся записанными в базу), но хорошо, что они закрываются и не выходят за рамки блока try-catch. А тем временем у нас «сверху» все еще есть активная транзакция.

      3) Использование «попытки» внутри активной транзакции позволяет продолжить выполнение кода после «пойманной» ошибки, но каждая попытка продолжить работу с СУБД (даже чтение) будет генерировать новую ошибку (вышеозвученную), пока транзакцию программно не закроют (или не прекратится выполнение кода, но тогда произойдет откат).


      1. alexeykny
        14.08.2018 13:26

        без использования попыток мы вообще не попадем на «В данной транзакции уже происходили ошибки»

        Всмысле?
        Просто после начала транзакции вызовите исключение и тгда после любого обращения к БД будет «В данной транзакции уже происходили ошибки»


        1. Dementor
          14.08.2018 14:00

          У вас код продолжает выполнение как ни в чем не бывало, если бросать неотлавливаемые исключения? Особая сборка платформы? :)


          1. alexeykny
            14.08.2018 16:30

            Да, точно, согласен. Без попытки никак )


  1. apapacy
    13.08.2018 19:58
    -4

    Я хотел пройти мимо єтого поста, но немного смутил такой вот незаметній комментарий с коде

    // Путевка в ад, серьезный разговор с автором о наших сложных трудовых отношениях.

    То есть Вы не только сами это используете но и других учите. Более того как бы незаметно намекаете что Вы не тоько учите но вершите там судьбы. Поэтому разберу немного В/аш подход.

    Вы пытаетесь обойти защиту от дурака которую разработчики 1с заложили в совю систему. Зачем Вы это делаете? Если транзакция началась (Вы же ее сами нгачинаете) и что-то внутри произошло то транзакция долна откатиться. В этом ее смысл и предназначение. если Вы хотите все же что-то сохранить. То не начинайте эту транзакцию.


    1. o4karek
      13.08.2018 20:51
      +2

      Автор не пытается обойти защиту от дурака. Автор пытается объяснить, как надо и как не надо пользоваться инструментом.


    1. EvilBeaver Автор
      13.08.2018 20:59
      +1

      Я не понял Ваш комментарий, простите


      1. apapacy
        13.08.2018 21:02
        -3

        Да ничего страшного. Я сам себя иногда не понимаю.


  1. EvilBeaver Автор
    13.08.2018 20:00

    «Попытка создает вложенную транзакцию, а Исключение ее откатывает»???

    О, Господи… ну вот вам, господа, и пример компетенций в отрасли.


  1. medvedevia
    13.08.2018 20:50
    +1

    Это давно уже есть в стандарте разработки, см. п.3.6 https://its.1c.ru/db/v8std#content:2149184148:hdoc


    1. EvilBeaver Автор
      13.08.2018 20:59

      Да, но нет в коде у разработчиков на местах. У них часто и подписки на ИТС нет, по которой эти стандарты можно прочесть. А Хабр читают все. Тут, глядишь, и заметят


      1. yukon39
        13.08.2018 22:16
        +2

        Так, да не так. В отличии от стандартов разработки, ваш подход, лично меня напрягает тем, как раз тем, что предполагет умышленное расхождение счета открытых и закрытых транзакций.

        Посмотрите внимательно — что произойдет, если «ДелаемЧтоТо()» будет написана по стандарту 1С и отменит транзакцию внутри себя? Транзакция в нашей функции не будет завершена и вывалится в исключение с незакрытой(!) транзакцией. В стандарте 1С такое исключено — при любых ошибках транзакция безусловно закрывается.

        Более того, где гарантия, что ЗафиксироватьТранзакцию() — самая главная часть нашей логики вообще выполниться? В приведенной методике ловить это прискорбное событие должна почему-то вызывающая функция, которой, в общем виде, до этого нет никакого дела.

        Вариант от 1С обеспечивает более целостную и модульную обработку транзакций и их ошибок, и гарантированно фиксирует или откатывает транзакцию.

        НачатьТранзакцию();
        Попытка
            ДелаемЧтоТо();
            ЗафиксироватьТранзакцию();
        Исключение
            ОтменитьТранзакцию();
            лог.Сообщение(лог_Ошибка, ПодробноеПредставлениеОшибки());
        КонецПопытки;
        


        1. EvilBeaver Автор
          13.08.2018 23:20

          Вы сейчас про какой мой фрагмент кода говорите? Просто я вроде как про то же самое пишу и не вижу каких-то противоречий


          1. yukon39
            14.08.2018 09:01

            Да, про код. Остальное все верно и я полностью с вами согласен.

            Вы в коде отчего-то предположили, что вложенная функция при ошибке непременно вызывает исключение или не обрабатывает возникающее внутри себя исключение. Это очень странная предпосылка — даже невнимательные 1С-ники все же пишут код который в норме не вызывает исключения или стараются хоть как-то обработать исключения в своём коде.


        1. 1div0
          14.08.2018 10:21

          Интересно, если ошибка произойдёт в ЗафиксироватьТранзакцию(), платформа разве её сама не откатит?
          Т. е. в этом случае мы не потеряем текст ошибки на строке ОтменитьТранзакцию()?


          1. yukon39
            14.08.2018 10:58

            Нет. Транзакция будет «битой» и любые последующий попытки её зафиксировать будут вызывать исключение, при этом ТранзакцияАктивна() будет возвращать Истина. Так что только ОтменитьТранзакцию() корректно сможет её завершить.


            1. 1div0
              14.08.2018 11:27

              Хмм… Это точно? Получается платформа неявно откатывает транзакцию при любых ошибках внутри неё, КРОМЕ данного случая.
              Получается ещё одно исключение из правил, которое нужно знать.


              1. yukon39
                14.08.2018 11:37
                +2

                А когда ещё платформа откатывает транзакцию кроме ОтменитьТранзакцию()? При возникновении ошибки внутри транзакции транзакцию становиться невозможно зафиксировать, но транзакция при этом всё еще активна.


              1. EvilBeaver Автор
                14.08.2018 12:39

                Нет. Платформа НИКОГДА не откатывает неявно транзакции при ошибках. Об этом и статья. При возникновении ошибки платформа транзакцию не отменяет и исключение летит вверх по стеку при все еще активной транзакции.


        1. mayorovp
          14.08.2018 11:00

          (комментарий удален)


        1. artbear
          14.08.2018 12:36

          Посмотрите внимательно — что произойдет, если «ДелаемЧтоТо()» будет написана по стандарту 1С и отменит транзакцию внутри себя?

          Вариант от 1С обеспечивает более целостную и модульную обработку транзакций и их ошибок, и гарантированно фиксирует или откатывает транзакцию.
          НачатьТранзакцию();
          Попытка
          ДелаемЧтоТо();
          ЗафиксироватьТранзакцию();
          Исключение
          ОтменитьТранзакцию();
          лог.Сообщение(лог_Ошибка, ПодробноеПредставлениеОшибки());
          КонецПопытки;

          Вижу противоречие или неточное формулирование
          Если в «ДелаемЧтоТо()» будет откат транзакции, которую не она начала, код вывалится по ошибке на фиксации транзакции.
          Автор статьи как раз и говорит, что нужно управлять только своими транзакциями, а не чужими.


          1. yukon39
            14.08.2018 13:13

            Даже если внутри «ДелаемЧтоТо()» по ошибке будет отменена наша транзакция, то совсем необязательно мы попадем в Исключение, но точно не сможем зафиксировать транзакцию.


            1. artbear
              15.08.2018 12:39

              Даже если внутри «ДелаемЧтоТо()» по ошибке будет отменена наша транзакция, то совсем необязательно мы попадем в Исключение, но точно не сможем зафиксировать транзакцию.

              Вы невнимательны :(


              я уже писал, напишу еще раз, чуть перефразировав:
              Если нет активной транзакции, что верно при нашем условии "в «ДелаемЧтоТо()» будет откат транзакции", тогда ЗафиксироватьТранзакцию само выдаст исключение :(


              в итоге мы в любом случае попадем в исключение.


              1. yukon39
                15.08.2018 15:48

                в итоге мы в любом случае попадем в исключение.

                Безусловно. Но есть момент:
                1. Мы в итоге не отменили свою транзакцию.
                2. Наше исключение вынужден ловить вышестоящий код, и он же должен решать что делать с нашей транзакцией. Это при том, что у него может быть и своя собственная транзакция, и если обработка транзакции написана так же, то проблема идет еще на уровень выше.

                Как-то это не очень масшабируемо и модульно.


      1. o4karek
        14.08.2018 11:59

        У них часто и подписки на ИТС нет, по которой эти стандарты можно прочесть.

        Методическая поддержка по платформе и стандарты разработки доступны без подписки (в отличие от док на разные продукты).


        1. EvilBeaver Автор
          14.08.2018 12:41

          Это действительно так, но народ не знает. «Раз URL стандартов начинается с „its.1c.ru“, то я даже и заходить не буду, все равно подписки нет» — примерно такие настроения в головах, когда посылаешь читать стандарты.


          1. Neikist
            14.08.2018 12:52
            +1

            Справедливости ради открыли они их недавно. Еще года полтора назад заходил — были закрыты. Не говоря уж про доки к БСП. А сейчас да, можно пользоваться, даже канал в телеграме есть с рассылкой отдельных пунктов и пояснениями.


            1. yukon39
              14.08.2018 13:14

              Ух ты, круто! Ссылку на канал можно?


              1. Neikist
                14.08.2018 13:47

                t.me/v8std
                Я так понимаю канал неофициальный, но ведет кто то из сотрудников.


                1. morv
                  15.08.2018 11:39
                  +2

                  Всё так, t.me/v8std веду я. Разбираю день по стандарту, чтобы было не скучно ехать в метро. Подписывайтесь!


            1. medvedevia
              14.08.2018 21:27

              Справедливости ради открыли они их недавно. Еще года полтора назад заходил — были закрыты.

              Пользуюсь стандартами разработки с 13 года. Не верите? Смотрите сами https://web.archive.org/web/20130417015349/https://its.1c.ru/#dev


              1. Neikist
                15.08.2018 07:21

                Перешел, почти все ссылки нужные серые (только глоссарий разработчика судя по цвету открыт, правда не прогружается инфа) При переходе по серым ссылкам окно авторизации.


            1. o4karek
              16.08.2018 10:56

              Методическая поддержка открыта давно — с августа 14 года.
              Стандарты действительно открыты недавно — с апреля 18 года.


      1. ZEEGIN
        14.08.2018 17:07

        EvilBeaver


        Так стандарты же примерно полгода назад открыли всем :)
        Читай без подписки на здоровье.


        Кстати в АПК проверка конкретно этого стандарта по транзакциям тоже реализована.


        1. EvilBeaver Автор
          15.08.2018 10:59

          Полгода назад — слишком мало, чтобы массы про это узнали. Я уже писал выше, что никто и не думает лезть в ИТС, зная, что там все закрыто.


          То, что ИТС постепенно открывается — это очень хорошо. Но популяризируете вы этот факт мало.


          1. ZEEGIN
            15.08.2018 11:12
            +1

            Ну https://github.com/VladFrost открыл канал в телеграмме t.me/v8std


            477 подписчиков сейчас


            Всем советую :)


        1. Infactum
          15.08.2018 11:02

          Осталось разрешить их поисковикам индексировать, чтобы народ чаще на всю эту информацию натыкался. Большинство сначала в Google полезут по любому вопросу.


          1. ZEEGIN
            15.08.2018 11:23

            Все ищется





  1. sancoder
    13.08.2018 23:15
    +1

    Исключение из ЗафиксироватьТранзакцию не уменьшает счетчик транзакций в платформе. И если метод, написанный с вашим подходом, находится в глубине стека прикладного кода, то есть риск того, что код выше по стеку будет получать ошибки, и проблема с «В этой транзакции уже происходили ошибки» повторится вновь.
    Поэтому рекомендуемый в документации подход, при котором ЗафиксироватьТранзакцию находится внутри Попытка/Исключение — более правильный.


    1. EvilBeaver Автор
      13.08.2018 23:27

      Если исключение в Зафиксировать не уменьшает счётчик, то Вы правы. Однако, я не могу придумать ситуацию при которой Зафиксировать бы выдавало исключение на активной транзакции, при значении счетчика равном тому, который установился при начале транзакции… Иными словами, если парность операторов работы с транзакциями не была нарушена, то Зафиксировать не должна бросать исключение. Я не помню на практике таких ситуаций. Просветите, пожалуйста


      1. sancoder
        13.08.2018 23:34
        +1

        В файловом варианте ЗафиксироватьТранзакцию кинет исключение при отсутствии места на диске. В серверном это сильно менее вероятно, но возможность окончания места для журнала транзакций СУБД — нельзя списывать.
        Да и вопрос не в том, когда ЗафиксироватьТранзакцию может стрелять исключением. Вопрос в том, что в вашей логике — изъян. И если я прав (а я прав :) ), то своей статьей вы провоцируете разработчиков 1С на неправильное поведение, т.к. вы стремитесь к тому, чтобы счетчик транзакций в начале и конце метода совпадал, а я предъявляю ситуацию, когда он не совпадает.


        1. EvilBeaver Автор
          14.08.2018 10:50

          Я, пожалуй, соглашусь. Фиксацию транзакции лучше разместить внутри попытки. Исправлю. Однако, настаиваю, что отмену транзакции в блоке Исключение, все-таки, нужно выполнять в условии, а не сразу. В статье пояснил почему — из-за риска затереть возникшую проблему новой.


          1. yukon39
            14.08.2018 11:14
            -1

            В статье пояснил почему — из-за риска затереть возникшую проблему новой.


            Если выставлять условие на отмену транзакции, то не выполняется требование безусловности фиксации или отмены транзакции внутри кода.
            Чтобы не породить новую проблему (исключение при отмене) вызов ОтменитьТранзакцию() тоже должен быть обернут в Попытка-Исключение.


          1. ZEEGIN
            15.08.2018 11:34

            Условие в блоке исключений нужно только если вызывать код написанный не по стандарту.
            В общем случае он не нужен.
            Потому все просто: если коду вызываемому доверяешь — проверка не нужна.


            1. EvilBeaver Автор
              15.08.2018 11:51
              +1

              От доверия до недоверия — один шаг. Я предпочитаю писать условие. Так код становится надежнее. А надежность — это хорошо.


              1. ZEEGIN
                15.08.2018 12:04

                Мой перфекционизм требует приводить к стандарту вызываемый код. :)


                1. EvilBeaver Автор
                  15.08.2018 12:35

                  Кто сказал, что стандартны перфектны (т.е. идеальны)?


                  1. ZEEGIN
                    15.08.2018 12:39

                    Не идеальны. Но они проверены специалистами разных групп и разработчиками платформы и согласованы между собой.
                    Все ошибки и пожелания к стандартам рассматриваются и исправляются в рамках партнерской конференции БСП (https://partners.v8.1c.ru/forum/forum/186/topics).


                    1. alexey-lustin
                      15.08.2018 12:57

                      Вот поэтому я и люблю sonarQube — он позволяет разработать правило и обсуждать конкретные места его срабатывания сразу в коде, а на форуме или на Хабре и самое главное он исключает холивар тимлидов, за счет разделения «авторов правил» и «команды разработки».

                      Про форум насколько я помню мы все в курсе ;-)


                      1. ZEEGIN
                        15.08.2018 13:02

                        sonarQube афигенен, жаль только что требуется лицензия на фичу открывающую проверку по нескольким бранчам, когда много версий на поддержке и когда хочешь тестировать до принятия мердж-реквета — это важно.


                        1. alexey-lustin
                          16.08.2018 01:50

                          Это не до конца верно. Вот смотрите — у нас лицензия Community, а бранчи есть opensonar.silverbulleters.org/project/issues?branch=develop&id=add&resolved=false

                          Причем на версии 7.2+ ;-)


                          1. ZEEGIN
                            16.08.2018 07:57

                            Появилась Community версия для бранч плагина? 0_о


                1. artbear
                  15.08.2018 12:50

                  Это уже другие затраты ресурсов, которые могут не иметь отношения к решаемой задаче.
                  мы же пишем код под свою задачу и, возможно, переиспользуем существующий код.
                  и правильнее написать код с проверкой, чем непонятно почему доверять коду, который "может быть" написан по стандарту.


                  изменение уже существующего и используемого кода может быть совсем тяжелым :(


                  PS если что, принцип "бойскаута" я люблю и сам применяю


                  1. ZEEGIN
                    15.08.2018 12:56

                    Понятное дело, но тут тоже можно поспорить :)
                    Есть база данных угроз ФСТЭКА, там есть замечательная угроза УБИ.165
                    https://bdu.fstec.ru/threat/ubi.165
                    Угроза включения в проект не достоверно испытанных компонентов


                    Переиспользование кода без испытаний влечет к понижению безопасности проекта, потому в любом случае надо делать аудит кода, и тогда уже принимать решение "байскаутить" или же пилить адаптер безопасного вызова.


      1. yukon39
        14.08.2018 09:25

        Я не помню на практике таких ситуаций. Просветите, пожалуйста


        Как минимум, один вендор СУБД прямо говорит, что при вызове фиксации или отмены транзакции всегда надо быть готовым поймать исключение:
        Try/Catch exception handling should always be used when committing or rolling back a SqlTransaction. Both Commit and Rollback generates an InvalidOperationException if the connection is terminated or if the transaction has already been rolled back on the server.


        Конечно, на практике такое встретить сложно, но ведь мы как раз о том как правильно работать с транзакциями, а не о том, что на практике что-то кто-то не видел.


        1. mayorovp
          14.08.2018 11:04

          На всякий случай уточню, что вы привели документацию по .net, а не по СУБД. Я что-то проспал и новая версия 1с на .net написана? :-)


  1. sancoder
    13.08.2018 23:33

    del


  1. yukon39
    14.08.2018 09:12

    Вот как правильно зафиксировать транзакцию более менее понятно. А как правильно её отменить?

    Очевидный вариант — предполагает два последовательных вызова ОтменитьТранзакцию().

    НачатьТранзакцию();
    Попытка
        ЧтоТо = ДелаемЧтоТо();
        Если ЧтоТо Тогда
            ЗафиксироватьТранзакцию();
        Иначе
            ОтменитьТранзакцию();
        КонецЕсли;
    Исключение
        ОтменитьТранзакцию();
        лог.Сообщение(лог_Ошибка, ПодробноеПредставлениеОшибки());
    КонецПопытки;


    1. mayorovp
      14.08.2018 11:05

      А чем кинуть исключение не вариант?


      1. yukon39
        14.08.2018 11:56

        Исключение в 1С это просто строка, т.е. сознательно бросать исключение нужно только с расчетом, что его увидит пользователь. Если код не предполагает уведомлять пользователя «матерным» окном на полэкрана, то исключения нужно отлавливать и записывать в лог самостоятельно. И желательно это делать по месту возникновения исключения, а не передавать обработку вышестоящему коду в надежде что уж тот код точно знает что это за исключение и что с ним делать.


        1. EvilBeaver Автор
          14.08.2018 12:43

          сознательно бросать исключение нужно только с расчетом, что его увидит пользователь.

          Блин, кто вам такое сказал? Сознательно бросать исключение нужно тогда, когда нужно, когда есть исключительная ситуация, с которой данный слой архитектуры не в состоянии справиться. Мы даже параметризовывали исключения и передавали в них Структуры, когда надо было.


  1. 1div0
    14.08.2018 10:11
    -1

    Привет!
    Хорошая статья. Единственно только хочу обсудить правильное решение по паттерну, где используется функция ТранзакцияАктивна().
    Чтобы выполнить обязательства по одному открытию и одному закрытию транзакции, может быть имеет смысл сначала этот признак записывать в переменную, а потом уже писать участок кода, работающий с транзакцией? Вот так:

    ОткрытьЗакрытьТранзакцию = НЕ ТранзакцияАктивна();
    
    Если ОткрытьЗакрытьТранзакцию Тогда
        НачатьТранзакцию();
    КонецЕсли;
    
    Попытка
        ДелаемЧтоТо();
    Исключение
        Если ТранзакцияАктивна() Тогда // здесь так, потому что 
                                       // транзакции может уже не быть
            ОтменитьТранзакцию();
        КонецЕсли;
        ВызватьИсключение ОписаниеОшибки();
    КонецЕсли;
    
    Если ОткрытьЗакрытьТранзакцию Тогда
        ЗафиксироватьТранзакцию();
    КонецЕсли;
    


    1. mayorovp
      14.08.2018 11:07

      Так ведь нет никаких обязательств по одному открытию и одному закрытию, есть лишь обязательства их парности.


      1. 1div0
        14.08.2018 11:35
        -1

        Замечание не относится к сути моего комментария и вопроса внутри него.


    1. ZEEGIN
      14.08.2018 17:43
      +2

      ВызватьИсключение ОписаниеОшибки();

      лучше не делать, просто ВызватьИсключение достаточно, так будет проброшено исходное исключение. Синтаксис доступен только в блоке catch


      На счет паттерна:


      1. ЗафиксироватьТранзакцию(); обязательно должен быть в блоке Попытка-Исключение
      2. Не должно быть логических операций между НачатьТранзакцию(); и Попытка
      3. ОтменитьТранзакцию(); должна быть первой операцией в блоке Исключение-КонецПопытки

      ТранзакцияАктивна() не обязательно использовать для начала новой транзакции, читайте документацию по транзакциям. Открытие новой транзакции в существующей — откроет вложенную транзакцию, закрытие — закроет вложенную, но нужна повторная фиксация чтобы закрыть основную (как раз ваш случай), отмена транзакции отменяет все, включая весь стек сложенных.


      Весь код приходит в вид:


      НачатьТранзакцию(); // Если транзакция открыта - откроется вложенная
      Попытка
          ДелаемЧтоТо();
          ЗафиксироватьТранзакцию(); // если это вложенная - произойдет переход к основной, иначе произойдет фиксация в базу
      Исключение
          ОтменитьТранзакцию(); // Отменятся все транзакции, даже если это вложенная
          ВызватьИсключение; // Выбросится изначальное исключение
      КонецПопытки;


      1. 1div0
        14.08.2018 18:47

        лучше не делать, просто ВызватьИсключение достаточно, так будет проброшено исходное исключение

        Спасибо за совет! Попробовал — яростно плюсую. Не знал что так можно.
        Когда автор так написал, я думал, что он просто сократил для наглядности.

        По поводу конструкции с условием, сегодня начал вспоминать, откуда оно у меня взялось? И вспомнил, что это была попытка использовать один код, который бы работал как в объектах с автоматическими блокировками, так и отдельно — с управляемыми (старая конфигурация). Нужен пример, но скорее всего там требовался просто более глубокий рефакторинг. Финальный вид, который приведён в статье — максимально правильный, и если появляется необходимость написать иначе — вопрос к правильности такой необходимости.

        По остальному, спасибо за объяснение. Я уже понял, что нужно так, почитал комментарии из верхних веток.


      1. 1div0
        14.08.2018 19:06

        Хотя похоже в финальной версии в статье вот это условие похоже тоже не нужно, я так понял оно всегда будет ИСТИНА:

        Попытка
            НачатьТранзакцию();
            ДелаемЧтоТо();
            ЗафиксироватьТранзакцию();
        Исключение
            Если ТранзакцияАктивна() Тогда // <--------
                ОтменитьТранзакцию();
            КонецЕсли;
            ВызватьИсключение;
        КонецПопытки;
        


        Это если посмотреть стандарт «Перехват исключений в коде», как Вы посоветовали ниже.


        1. EvilBeaver Автор
          15.08.2018 11:02

          Это условие далеко не всегда будет Истина, к огромному сожалению. Поэтому и статья написалась.


        1. ZEEGIN
          15.08.2018 11:37

          Если в ДелаемЧтоТо(); будет ОтменитьТранзакцию(); без НачатьТранзакцию(); то будет проблема.
          Но это значит, что ДелаемЧтоТо() написан не по стандарту, т.к. не соблюдает парность операций.
          Потому проверка для всего кода написанного по стандарту не нужна, а если вызываешь чей-то код, которому не доверяешь — то либо надо сделать проверку, либо, поступить правильно и переписать вызываемый ДелаемЧтоТо() по стандарту.


      1. 1div0
        14.08.2018 19:18

        Получается действительно самый правильный вариант такой, и он мне нравится:

        НачатьТранзакцию();
        Попытка
            ДелаемЧтоТо();
            ЗафиксироватьТранзакцию();
        Исключение
            ОтменитьТранзакцию();
            ВызватьИсключение;
        КонецПопытки;
        


        1. artbear
          15.08.2018 12:56

          И опять двадцать пять :(
          Этот вариант по стандарту, но он может непредсказуемо падать, если ДелаемЧтоТо или его внутренности работают неверно.
          Последствия:
          1 И обнаружится это только в рантайме
          2 и исходное исключение будет перекрыто исключением, возникающим при отмене транзакции в нашем же блоке исключения
          т.е. мы тупо потеряем весь контекст проблемы :(
          3 и наверняка возникнет там, где сложно будет проверить и восстановить исходную ситуацию с падением


          нам проверку на активность транзакции диктует опыт использования различных конфигураций 1С, в т.ч. и типовых от 1С, написанных/доработанных специалистами разного же уровня, за многие годы использования 1С.


          1. yukon39
            15.08.2018 15:33
            +1

            И мы плавно идем к тому, что не только ЗафиксироватьТранзакцию() может вызвать исключение, но и ОтменитьТранзакцию(). Следовательно, обе функции должны быть обрамлены в Попытка-Исключение, а условие на ТранзакцияАктивна() лишь попытка избежать неминуемого.

            И, тут в комментариях есть фрагмент документации от Microsoft который именно так и рекомендует делать.

            Про стандарты есть момент, что стандарт это не данное нам откровение свыше и вполне может меняться.


          1. yukon39
            15.08.2018 15:59

            Даже пример кода есть:

            transaction = connection.BeginTransaction("SampleTransaction");
            try
            {
                doWhatever();
                transaction.Commit();
            }
            catch (Exception ex)
            {
                Console.WriteLine("Message: {0}", ex.Message);
                try
                {
                    transaction.Rollback();
                }
                catch (Exception ex2)
                {
                    Console.WriteLine("Message: {0}", ex2.Message);
                }            
            }


            И весьма прозрачно транслируется в код 1С:
            НачатьТранзакцию();
            Попытка
                ДелаемЧтоТо();
                ЗафиксироватьТранзакцию();
            Исключение
                Сообщить(ОписаниеОшибки());
                Попытка
                    ОтменитьТранзакцию();
                Исключение
                    Сообщить(ОписаниеОшибки());
                КонецПопытки;
            КонецПопытки;


            1. artbear
              16.08.2018 12:28

              Интересно, да.

              1 Но все-таки ОтменитьТранзакцию имеет намного-намного меньше шансов упасть, если мы предварительно убедимся, что есть открытые транзакции через Транзакция Активна.

              2 И пример нехороший совсем.
              Зачем «Сообщить» то? и где переброс исключений на верхний уровень? и т.п. и т.д.
              явное зло.


              1. ZEEGIN
                16.08.2018 12:45

                Не понимаю чем Сообщить лучше ВызватьИсключение.
                Обработка вызова исключения корректно дойдет до пользователя и покажет ошибку.
                Обрабатывать дополнительно ошибку отмены транзакции не нужно.


                Имеет смысл код обработки ошибки отмены транзакции только в случае, когда результат кода должен дойти куда-нубудь вне инетрефейса 1С, например возвратов веб-сервиса или http-сервиса.


                Но даже там проще обернуть в попытку исключение вызов бизнес-логики чтобы корректно сформировать код возврата вне зависимости от того ошибка это базы или бизнес-логики, потому что конечно потребителю это собственно все равно, ему главное получить код статуса ошибки.


                1. yukon39
                  16.08.2018 14:05

                  Обрабатывать дополнительно ошибку отмены транзакции не нужно.

                  Не нужна Попытка как таковая, или не нужно ничего делать в Исключение-КонецПопытки?


                  1. ZEEGIN
                    16.08.2018 14:28

                    Если ничего не нужно делать в блоке исключений (как с отменой транзакции) то и попытка не нужна. Гасить исключения бесследно — плохой тон. Есть очень редкие случаи, когда исключение не приводит к проблемам и можно его заменить на запись в журнал регистрации типом предупреждение, но в общем исключение должно быть выброшено пользователю и при этом платформа сама его запишет в журнал как ошибку.


                    1. yukon39
                      16.08.2018 14:38

                      Гасить исключения бесследно — плохой тон.

                      Какой уж там «плохой тон». Это прямо запрещено стандартом "Перехват исключений в коде":
                      3.4. Недопустимо перехватывать любые исключения, бесследно для системного администратора


                      Про запись в ЖР там тоже есть.


              1. yukon39
                16.08.2018 13:35

                открытые транзакции через Транзакция Активна

                Нарушается принцип безусловности — если мы открыли транзакцию, то мы должны или зафиксировать её либо отменить.

                Если ОтменитьТранзакцию() выдаст исключение, то значит ДелаемЧтоТо() написана неправильно, и, чем раньше по стеку вызовов мы это отловим, тем лучше.

                Зачем «Сообщить» то?

                В конкретном примере это «калька» с кода выше, не более.

                и где переброс исключений на верхний уровень?

                А он точно есть? Может этот код прямо из команды формы вызывается — и пользователь получит «красивое» окно на полэкрана.

                Наверно все же запись в ЖР обязательна, а необходимость выброса исключения зависит от бизнес-логики. В стандарте "Перехват исключений в коде" есть три варианта развития событий:
                1. Вызов исключения
                2. Использование кодов возврата
                3. Перехват исключения незаметно для пользователя


                1. ZEEGIN
                  16.08.2018 14:31

                  Окошко действительно красивое. Если платформа запущена под отладкой — то доступен стек и пепеход к дизайнеру, но если запущена в штатном режиме, то это окно как обычное предупреждение.


                  1. yukon39
                    16.08.2018 14:48

                    Окошко ни разу не красивое. Если оно появилось у пользователя, то значит разработчик чего-то не досмотрел. И опять же в стандарте есть пример реализации обработки исключения при вызове серверного кода с клиента.

                    Хотя есть случаи когда действия по стандарту приводят к исключению, которое нельзя перехватить и пользователь на себе ощущает всю дружелюбность интерфейса.

                    Для отладки есть отличный флаг «Останавливаться по ошибке» как раз для таких случаев.


                    1. ZEEGIN
                      16.08.2018 15:00

                      Я не об этом. Я о том, что в зависимости от того разрешена ли отладка в текущем сеансе окно меняется.


                      Обработка:


                      &НаСервереБезКонтекста
                      Процедура ТестВызватьИсключениеНаСервере()
                      
                          ВызватьИсключение НСтр("ru = 'Тест'");
                      
                      КонецПроцедуры
                      
                      &НаКлиенте
                      Процедура ТестВызватьИсключение(Команда)
                      
                          ТестВызватьИсключениеНаСервере();
                      
                      КонецПроцедуры
                      

                      Если включена:


                      Если отключена:


  1. movsb
    14.08.2018 10:34

    Имя процедуры не должно быть ОченьПолезныйИВажныйКод. Это четвертая, но самая важная ошибка. Уважаемый автор, с таким именем Вы как бы намекаете на то, что эту процедуру можно расположить где угодно и вызывать из любого места. Вы так яростно сражаетесь с вложенными транзакциями! Остановитесь на минуту. Вложенные транзакции не поддерживаются! Из этого факта нужно сделать один простой вывод: их не нужно допускать на уровне архитектуры приложения.

    Сценарий транзакции должен начинать транзакцию и завершать ее, находясь в начале стека выполнения на сервере. Вызов извне будет только из клиентского события. Тогда не нужно оборачивать транзакцию в попытку, так как она откатится естественным образом. И никаких НачатьТранзакцию в глубине стека! Можно вызывать процедуры модификации данных, но они не должны начинать транзакции. Только код обработки клиентского запроса на верхнем уровне знает, когда начинается транзакция – собственно, когда обрабатывается клиентский запрос.


    1. EvilBeaver Автор
      14.08.2018 10:48

      никаких НачатьТранзакцию в глубине стека

      Вот в корне не соглашусь! Это усложняет изоляцию модулей, увеличивает сложность и вообще…


      1. movsb
        14.08.2018 11:30

        Изоляция модулей идет в разрез с двумя свойствами транзакции – атомарностью и согласованностью. Между началом и завершением транзакции все изменения данных, в том числе вложенные подоперации, должны быть согласованны.


        1. EvilBeaver Автор
          14.08.2018 12:44

          Не вижу противоречий между ACID и изоляцией модулей. Раскройте мысль, пожалуйста.


          1. movsb
            14.08.2018 13:57

            Хорошо, пусть у нас есть две процедуры, которые построены аналогично ОченьПолезныйИВажныйКод. Процедура ИзменитьОкладыСотрудников устанавливает новые оклады в справочнике сотрудников, и все это во вложенной транзакции. Вторая процедура ИзменитьОкладыДолжностей записывает оклады в справочник должностей, тоже во вложенной транзакции. Задача написать обработку Индексация, которая устанавливает новые оклады для сотрудников и их должностей.
            Если вызвать эти процедуры последовательно, понадеявшись на их корректность, мы потеряем атомарность. Например, ИзменитьОкладыСотрудников сработает, а ИзменитьОкладыДолжностей – нет. В итоге в справочнике сотрудников будут новые оклады, а в справочнике должностей – старые. Нельзя менять оклады сотрудников, не меняя оклады должностей, и наоборот. Эти данные должны быть согласованы. Поэтому нужно главную процедуру обработки выполнять в транзакции.
            А внутренние транзакции не имеют смысла. Зачем в процедуре, которая что-то выполняет и сделана для вызова из других процедур, внутренняя транзакция? Все равно будет более общая транзакция, которая объединит все модификации данных и сделает это согласованно. В итоге получается такой вариант:

            Процедура ОченьПолезныйИВажныйКод(СписокСсылокСправочника)
                Для Каждого Ссылка Из СписокСсылокСправочника Цикл
                    ОбъектСправочника = Ссылка.ПолучитьОбъект();
                    ОбъектСправочника.КакоеТоПоле = "Я изменен из программного кода";
                    ОбъектСправочника.Записать();
                КонецЦикла;
            КонецПроцедуры
            

            Список ссылок нужно получать из запроса ДЛЯ ИЗМЕНЕНИЯ.


            1. yukon39
              14.08.2018 14:06

              Список ссылок нужно получать из запроса ДЛЯ ИЗМЕНЕНИЯ.

              Кхм, автоматические блокировки? В 2018 году? Сурово.


              1. 1div0
                14.08.2018 19:00

                Да запросто, если компания использует например модифицированную УТ10.3. Переезд на актуальную версию — весьма желанен. Но хочется это сделать нормально. Ждём бизнес-процессы в расширениях.


            1. EvilBeaver Автор
              14.08.2018 14:36

              Нужна более объемлющая операция, объединяющая эти две. Все просто и естественно

              Попытка
                  НачатьТранзакцию();
                  ИзменитьОкладыСотрудников(); 
                  ИзменитьОкладыДолжностей();
                  ЗафиксироватьТранзакцию();
              Исключение
                 // здесь код из статьи
              КонецПопытки;
              


              В примере нам неважно — есть ли вызовы НачатьТранзакцию внутри операций. Нам важно, что ОБЕ они должны быть выполнены атомарно. Это обеспечивает обрамляющая транзакция, а внутренние транзакции этих методов становятся просто «увеличителями счетчиков». Таким образом целостность обеспечивается просто и прозрачно.

              Обычная комбинаторика модулей и повторное использование, на этом держится весь IT.


              1. movsb
                14.08.2018 14:43

                А что дают внутренние транзакции?


                1. EvilBeaver Автор
                  14.08.2018 15:36

                  Они дают атомарность на уровне абстракции метода ИзменитьОкладыСотрудников.

                  Предположим, у вас в этом методе должны атомарно выполниться изменения по всем сотрудникам сразу. И этот метод вы можете вызывать, как в рамках указанного примера (вместе с должностями), так и сам по себе — только по сотрудникам. Тогда этот метод изолирован и независим. Он внутри транзакционно по всем сотрудникам делает начисления. А еще, его можно повторно использовать в более крупной операции, когда и по сотрудникам и по должностям меняем оклады.


              1. ZEEGIN
                14.08.2018 17:45
                -1

                RollBack забыл первой строкой в исключении


    1. yukon39
      14.08.2018 11:07

      Т.е. вы допускаете наличие неявных вложенных транзакций, но против явных? Это, как минимум, непоследовательно.


      1. movsb
        14.08.2018 11:19
        -1

        Не должно быть ни явных вложенных транзакций, ни неявных (Попытка внутри транзакции).


        1. yukon39
          14.08.2018 11:30

          И как записать что-либо в базу без неявной транзакции? Как проконтролировать, что внутри неявной транзакции нет других неявных транзакций (например при проведении документа не записываются наборы записей каких-либо регистров)?


          1. movsb
            14.08.2018 11:48

            Что значит без неявной? Запись объектов выполняется в неявной транзакции. А вот проконтролировать, что внутри транзакции нет вложенных транзакций, может только разработчик. Если он, конечно же, станет придерживаться архитектуры приложения без вложенных транзакций (как явных так и не явных) внутри основной транзакции (явной или неявной).


            1. yukon39
              14.08.2018 12:03

              Вот именно. Запись объектов всегда производится в неявной транзакции. Внутри неявной транзакции могут быть другие неявные транзакции, которые тоже могут иметь свои вложенные неявные транзакции. И это не только допустимо, но и нормально для платформы.

              Почему вы отказываете в праве вложенности явным транзакциям?


              1. movsb
                14.08.2018 12:38

                Во-первых, вложенные они только на уровне синтаксиса. Всегда действует только транзакция самого верхнего уровня.

                А во-вторых, во вложенных транзакциях нет никакой необходимости. Атомарность – первое свойство транзакции. Изменения данных инициирует клиентский запрос, который может быть вызван нажатием кнопки в форме, да и вообще любым интерактивным действием, это может быть вызов web-сервиса или запуск платформы из bat-файла с автоматическим выполнением обработки, обращение через COM-соединение и так далее. Во всех этих случаях есть главная процедура, которая обрабатывает клиентский запрос. Именно она и должна начинать и фиксировать транзакцию. С точки зрения пользователя вложенных транзакций нет. Он либо получает атомарно изменения, которые инициировал, либо нет. В этом и заключается атомарность.


        1. EvilBeaver Автор
          14.08.2018 12:50

          Попытка внутри транзакции это просто try-catch и ведет себя он ровно так же, как и вне транзакции. Почему вы приплетаете Попытки к механизмам неявных транзакций??


  1. Dream2
    14.08.2018 13:56

    Да, соглашусь этот вариант правильный, и он мне нравится:

    Попытка
        НачатьТранзакцию();
        ДелаемЧтоТо();
        ЗафиксироватьТранзакцию();
    Исключение
        Если ТранзакцияАктивна() Тогда
            ОтменитьТранзакцию();
        КонецЕсли;
        ВызватьИсключение;
    КонецПопытки;

    Вот только не понимаю, зачем нужно
    ВызватьИсключение;


    1. EvilBeaver Автор
      14.08.2018 15:41

      Чтобы не терять стек вызовов исходного исключения


    1. ZEEGIN
      14.08.2018 17:48
      +1

      Хватит изобретать велосипед, есть пример в стандарте "Перехват исключений в коде"


      3.6. При использовании транзакций следует придерживаться следующей схемы обработки исключений в коде на сервере:


      // 1. Начало транзакции
      НачатьТранзакцию();
      Попытка
       // 2. Вся логика блокировки и обработки данных размещается в блоке Попытка-Исключение
       Запрос = Новый Запрос("...");
       Выборка = Запрос.Выполнить().Выбрать();
       Пока Выборка.Следующий() Цикл
        ... 
       КонецЦикла;
      
       // 3. В самом конце обработки данных выполняется попытка зафиксировать транзакцию
       ЗафиксироватьТранзакцию();
      Исключение
       // 4. В случае любых проблем с СУБД, транзакция сначала отменяется...
       ОтменитьТранзакцию();
       // 5. ...затем проблема фиксируется в журнале регистрации...
       ЗаписьЖурналаРегистрации(НСтр("ru = 'Выполнение операции'"), УровеньЖурналаРегистрации.Ошибка,,, ПодробноеПредставлениеОшибки(ИнформацияОбОшибке()));
       // 6. ... после чего, проблема передается дальше вызывающему коду.
       ВызватьИсключение;
      КонецПопытки;

      Поскольку исключение не отменяет транзакцию сразу, но запрещает успешное завершение транзакции, то все вызовы НачатьТранзакцию, с одной стороны, и ЗафиксироватьТранзакцию или ОтменитьТранзакцию, с другой стороны, должны быть парными.


      Отсутствие обработки исключительных ситуаций приводит к зависшим транзакциям и к сложнодиагностируемым ошибкам вида «В этой транзакции уже происходили ошибки» в произвольных местах кода. При этом в случае вложенных операторов НачатьТранзакцию, следует обеспечить вызов всех парных операторов ОтменитьТранзакцию. Для этого в конце блока Исключение необходимо пробросить исключение выше по стеку с помощью ВызватьИсключение (как в примере выше) и соответствующим образом обработать исключение на каждом уровне стека.


      1. Infactum
        15.08.2018 09:23

        Этот пример тоже не идеален, но не с точки зрения работы с транзакции, а с точки зрения работы с исключениями.
        Нормальный программист не будет помещать код, который не должен генерировать исключений в try-catch, как например это сделано с созданием объема Запрос в вашем примере. Ровно так же, как и перехват исключений потенциально разной природы (к сожалению типизации в 1С нет) одним блоком — плохой стиль.
        Потом, привыкнув, программисты начинают оборачивать вызовы функций в try-catch, и сиди потом, гадай, действительно там предполагается выброс исключений или они просто от «быдлокода» страхуются.


        1. ZEEGIN
          15.08.2018 11:41

          Что верно то верно.
          Но работаем с тем, что имеем.


          Сколько раз видел, как методы БСП оборачивают в try-catch, не смотря на то, что эти методы были спроектированы так, чтобы никогда не выбрасывать исключений...


          Как с этим бороться? Пока ответа нет.


  1. DonAlPAtino
    14.08.2018 14:46

    Вы ссылочку-то на нормальный workround как настроить ELKу для журнала 1С от начала до конца дайте что ли. Только, пожалуйста, не на любимый infostart, где все сводиться к запуску некоего поделия, написанного на VB и выгружающего raw данные, которые потом надо как-то оттранслировать так, чтобы ELKа показывала журнал в том же виде, что и 1С. А то ваши коллеги одинэсники такой ёлкой пользоваться отказываются. А пара сисадминов — не одинэсников уже месяц с выгрузкой справиться не могут. Хоть и все на infostart'е прошерстили. Я вот влез в это дело и очень понял почему ELK-стек под 1С только единицы настраивают.


    1. EvilBeaver Автор
      14.08.2018 15:37

      Мы настраиваем и/или консультируем за деньги. Есть дешевые варианты, есть красивые — на любой вкус.


      1. DonAlPAtino
        14.08.2018 15:44
        +1

        Что и объясняет почему никто нормально настроить не может. Те кто знают «как правильно» пилят бабло, а остальные мучаются. Ну и еще с вашей т.з. очевидно «не очень профессиональные люди, которые не могут нормально настроить систему». Вы бы тогда уж, лучше, про ELK промолчали.


        1. EvilBeaver Автор
          14.08.2018 16:02

          Ну и еще с вашей т.з. очевидно «не очень профессиональные люди, которые не могут нормально настроить систему»


          Не понял этот тезис. Я не говорил такого. Постараюсь раскрыть: ELK стек нужен тем, у кого много логов, т.е. на крупных внедрениях, тем у кого есть как минимум лишний сервер под Elastic и прочее. То есть, это чисто корпоративный продукт. И вполне нормально, когда компания просто покупает себе систему хранения логов от 1С не тратя собственные ресурсы на разработку и хождение по граблям. Не совсем ясно, в чем Ваша претензия?


          1. DonAlPAtino
            14.08.2018 16:08

            Я вот месяц с настройкой мучаюсь. Нормально не взлетает.Наверное из-за этого меня фраза «А журнал регистрации, как вы знаете, медленный, а ELK-стек для журналов 1С у нас в отрасли настраивают единицы…» очень уж покоробила. Извините.
            Единицы настраивают не потому что не хотят или ленятся. А потому что фиг настроишь.


            1. EvilBeaver Автор
              14.08.2018 16:15

              Да, задача непростая. Но решаемая. И камень был не в огород тех, кто настраивает, но пока не удалось. Камень был в огород тех, кто даже не пытается настраивать.


              1. DonAlPAtino
                14.08.2018 16:42

                Ну так помогли бы. Если уж не инструкцией, то хотя бы общим направлением. А то пока у меня вышел многозвенный монстр с промежуточной трансляцией на sql сервере, который сегодня этот сервер и положил не переварив объем журнала…


                1. EvilBeaver Автор
                  14.08.2018 17:02

                  Я вроде и так помог с направлением :) b2b@silverbulleters.org

                  Если серьезно, то это тема для отдельной статьи или разработки. Эти знания не то чтобы сильно уникальны, но действительно единичны. Мы стараемся продавать ELK-стек для 1С, как услугу. Это не жадность и не «пилить бабло», обычное коммерческое решение, и я не вижу нет ничего крамольного, в том чтобы продавать ПО и его поддержку.

                  Мы можем настроить ELK или GrayLog или любую другую систему обработки логов, сэкономив компании человекомесяц классного специалиста. Разве это плохо?


                1. EvilBeaver Автор
                  14.08.2018 17:12

                  Ну и еще один момент: те кто нас знает давно, в курсе нашей политики. Мы охотно помогаем тем, кто помогает другим. Например, вы контрибьютор в наши open-source продукты. Или вы написали обучающую статью, которая помогла другим специалистам. Или вы еще каким-либо образом приносите пользу сообществу. Тогда помочь — святое дело. А подход «Дайте мне» — не работает. С чего бы это вдруг? А вы нам что?


                  1. JohnyD
                    14.08.2018 17:49
                    +1

                    Подтверждаю.
                    Теперь и я за деньги настраиваю ELK, о котором мне когда-то поведала пуля. Намучался я с ним, правда, достаточно сильно и много.
                    Кстати, про ELK и 1С можно было много услышать на хакатоне isthisdesign.org. Там ~4 способа отправки ЖР в ЕЛК разобрали


    1. alexey-lustin
      15.08.2018 05:23

      Я вот влез в это дело и очень понял почему ELK-стек под 1С только единицы настраивают


      Причин несколько — ключевых три:

      * ELK, а на самом деле ELKG композитное приложение которое на данный момент подразумевает использование beats как основного сборщика журналов приложений, а там golang пусть и упрощенный с помощью BeatsSDK. Типичного 1С-ника тянет обратно к 1С или на худой конец SQL. Причина номер один: нежелание разбираться с базовой архитектурой Beats(Logstash)+ElasticSearch+KIBANA/GRAPHANA

      * До декабря 2017 года все наработки по сборщикам на базе ruby и golang лежали в открытом доступе на github под открытой лицензией. Там же лежали grok выражения консолидировано созданные тремя авторами. Там же был подготовленный vagrantfile для тестового стенда и docker-compose для развертывания в режиме docker-swarm. За 2 года существования ни одного контрибьютора к развитию данного проекта так и не присоединилось — наработки в итоге были перенесены в закрытый контур как часть уже коммерческой деятельности. Причина номер два: отсутствие философии краудсорсинга в голове у большинства 1С специалистов (как показывает практика такая проблема наблюдается не только у 1С-сообщества) — новый контрибьютор с активным pull-request'ом почти как «новый год», вызывает бурю восторга и желание закатить вечеринку дней на 5, настолько это редкая история. В итоге каждый пилит своё и персонально для себя/своей конторы и своей архитектурой — но этих людей по рынку около 10 не больше и все так или иначе находятся либо на Инфостарте, либо на Github.

      * запуск «централизованного хранилища журналов и их анализа» — это проект, 1С это только одно приложение, а целевая архитектура ELKG предполагает что НЕ только 1С журналы — технологический и регистрации должны попадать в «индексы», но и журналы web сервера, сервера СУБД, операционной системы и т.д. Причина номер три: непонимание границ проекта в целом и его целей, в итоге попытка создать костыль в стиле «щас я загружу журнал регистрации в ELKG и пойду пить пиво, а зачем это нужно пусть админы разбираются»

      Такова реальность на сегодня. Как один из авторов наработок я пока не вижу предпосылок к её изменению.

      Чтобы мой комментарий не выглядел как менторство или как комментарий «жестокого» капиталиста вот что я могу вам посоветовать — вам не нужно промежуточное хранилище на SQL. ElasticSearch сам прекрасно справляется с задачей проможуточных хранилищ индексов с поддержкой реиндексации с возможностью скриптинга на сервера

      Ссылка для понимания www.elastic.co/guide/en/elasticsearch/reference/current/docs-reindex.html

      Ссылка даю в рамках надежды что вдруг вы станете контрибьютором в сообществе и вспомните нас добрым словом и делом.


      1. DonAlPAtino
        15.08.2018 09:18

        Алексей, спасибо за развернутый ответ. Я в целом не «одинэсник» и этим проектом занялся, т.к. «люди в желтых майках» из своего привычного мирка не выползают. Их и там не плохо (я бы сказал даже неоправданно хорошо) кормят. А если что не так, то всегда можно свалить на «сисадминов». Для реиндексации сразу в Эластик сисадминам уже квалификации очевидно не хватает :-(
        Ну и, к слову, куда контрибьютить, если вы закрыли проект на github?


  1. sancoder
    14.08.2018 22:43

    Вы меня извините за занудство, но зачем вы НачатьТранзакцию в финальном варианте засунули внутрь Попытка/Исключение?
    Если исключение из НачатьТранзакцию (платформа не увеличит счетчик транзакций), то вы откатите чужую транзакцию при обработке исключения.
    Хотя на практике я не видел, чтобы НачатьТранзакцию приводило к исключению.


    1. EvilBeaver Автор
      15.08.2018 06:56

      Засунул для красоты. Так отступы красивее )


      1. sancoder
        15.08.2018 07:45
        +1

        Красота — это предмет третьей степени важности (первый — правильность, второй — производительность).
        Так вот, на 3 из 4 поддерживаемых СУБД, при вызове НачатьТранзакцию платформа идет в СУБД и открывает там транзакцию. В этот момент может произойти все что угодно: нехватка памяти для очередного соединения с СУБД, разрыв связи, ошибка дисковой подсистемы.
        Давайте все-таки делать, как написано в документации: НачатьТранзакцию до начала Попытки, Зафиксировать — внутри, Откатить — в обработке исключения. Стандартная для всех языков практика, в общем-то. У вас статья как называется?


        1. EvilBeaver Автор
          15.08.2018 10:41

          Стоп-стоп. Чем принципиально хуже размещение Начать внутри попытки? Если НачатьТранзакцию() выкинет исключение, то это будет какое-то неперехватываемое исключение работы с СУБД, одно из тех, о которых упоминал o4karek, и которое все равно убъет сеанс целиком, независимо от всяких «Попыток».

          А если и будет «перехватываемое» исключение, то в блоке Исключение оно также безопасно будет отработано за счет условия с ТранзакцияАктивна. Получается что мой вариант надежен и так и этак. А надежный код — это хорошо.

          Насчет производительности — опять непонятно. Как принципиально влияет на производительность размещение НачатьТранзакцию до или внутри Попытки?


          1. sancoder
            15.08.2018 16:46
            +1

            Если будет перехватываемое исключение из НачатьТранзакцию, то вы откатите транзакцию не своего уровня. Вы же не проверяете ТранзакцияАктивна до начала транзакции. С чего вы взяли, что ваша транзакция — самая первая?

            Насчет производительности — здесь неактуально. Вы написали, что руководствовались эстетическими соображениями для переноса строчки кода. Я же рассматривал более общий случай.


            1. EvilBeaver Автор
              15.08.2018 17:05

              Ок, принято. Хотя науке и неизвестны случаи выброса перехватываемых исключений из НачатьТранзакцию() — если они есть, то Начать лучше расположить ДО попытки.


              1. sancoder
                15.08.2018 17:13

                Вы исходите (исходили) из того, что есть только существующая платформа и все, она не развивается. А что, если в следующей версии будет какая-то доработка, которая добавит возможность, которой раньше не было. И в рамках этой возможности вопрос станет актуальным. Что, обновлять статью, и изменять все конфигурации?


  1. grumegargler
    14.08.2018 23:31

    Странно, что никто не обращает внимание на то, сколько не связанного с логикой транзакций кода, попадает под попытку, при подходе ИТС…

    По статье. Первая ошибка немного притянута за уши, потому что метод Заблокировать() в свою очередь также кидает исключение, ровно как и неудачный Записать ().

    Еще два вопроса практического характера.

    Вдруг он там взял, да и вызвал метод ОтменитьТранзакцию или наоборот, зафиксировал ее? и финальный вариант:
    Попытка
        НачатьТранзакцию();
        ДелаемЧтоТо();
        ЗафиксироватьТранзакцию();
    Исключение
        Если ТранзакцияАктивна() Тогда
            ОтменитьТранзакцию();
        КонецЕсли;
        ВызватьИсключение;
    КонецПопытки;

    На каком основании делается вывод, что ДелаемЧтоТо(); начал и не закончил транзакцию лишь однажды, а если это произошло два или более раз?

    Подскажите пожалуйста (отбросив на время все прописные истины) на практическом примере пагубность использования подхода, при котором мы будем заботиться о транзакциях там, где это нужно, а не на всякий случай везде. Давайте на минутку представим, что во всей конфигурации мы не втыкаем попытки/исключения там где видим НачатьТранзацию, а отматываем транзакции там, где логика кода позволяет случаться исключениям, ведь таковых существенно меньше, чем первых, не говоря о том, что можно допустить механическую ошибку.
    Как пример: перепроведение документов; мы только в том куске кода, который отвечает за “записать” — делаем попытку/исключение с отматыванием тразнаций через ТранзакцияАктивна () в цикле. Отматывая их назад 1) Мы не можем утверждать, что мы отмотали чужую транзакцию (до нас), это мог сделать вызывающий нами ненадежный код 2) Отсутствие вложенности транзакций заставляет их открутить до 0 каждый раз, перед тем, как продолжить работать с базой.
    На счет комментария:
    Отменять эту внешнюю транзакцию ваш код не должен, так как внешний код сам должен обрабатывать свои транзакции!

    Мне не хватает практического опыта, чтобы подтвердить это правило, учитывая, что ЗафиксироватьТранзакцию () вне любой другой пары просто работает как счетчик, ничего не фиксируя в базе.


    1. alexey-lustin
      15.08.2018 04:34

      мы будем заботиться о транзакциях там, где это нужно


      Я это читаю как

      Я очень умный специалист, эти ваши стандарты кодирования для юных падаванов, я сам решу где мне и что писать


      я ни в коем случае не перехожу на личности, я просто пытаюсь указать на то, что формулировка

      там, где это нужно


      неизбежно приводит нас к вопросу «А где нужно?», формируя ответ мы будем вынуждены разработать правила «Где нужно, а где не нужно» для команды (вы же не в одиночку работаете). А это уже другое правило нежеле чем про транзакции и их парность и исключительность…

      Я имею ввиду например: что «Не рекомендуется использовать циклы по условно бесконечной выборке внутри операторных скобок Начать и Зафиксировать транзакцию» по причине деградации производительности в многопользовательских системах.


      1. grumegargler
        15.08.2018 06:12
        -1

        Я это читаю как

        Я очень умный специалист, эти ваши стандарты кодирования для юных падаванов, я сам решу где мне и что писать

        гм… не знаю что заставляет так думать, надеялся на сугубо профессиональный ответ.
        неизбежно приводит нас к вопросу «А где нужно?»

        Мы с коллегами предпочитаем писать код, который понимаем (пусть даже не всегда правильно), чем писать код, который мы не понимаем, но зато по стандартам. Поэтому вопросов, где нам нужно класть транзакцию в исключение у нас нет, потому что это часть логики программы. А исходный вопрос, по сути, очень прикладной: вставлять везде начать транзакцию в попытку можно механически и забыть (не будем сейчас говорить о анализаторе не анализируемого в принципе кода), а вот если мы понимаем, где нам нужно перехватывать её (если вы не понимаете где — продумайте вначале алгоритм) — ошибиться уже сложней.
        В любом случае, не хотелось бы просто цитат, был бы очень полезным практический пример.
        «Не рекомендуется использовать циклы по условно бесконечной выборке внутри операторных скобок Начать и Зафиксировать транзакцию»

        Очень интересно, особенно если учесть, что кол-во этих вызовов в итоге будет одинаковым, просто вынесенным из исключений нижестоящих вызовов.


        1. alexey-lustin
          15.08.2018 06:56

          Уже не первый раз замечаю, что у вас какая-то личная неприязнь сквозит в комментариях… Вы не читаете что я пишу. Попробую еще раз:


          • есть правило на определенную языковую конструкцию — описана в статье. это только одно правило
          • есть места в вашем прикладном коде где это правило применилось и выявило несоответствие реального кода правилу
          • у каждого конкретного места есть результат расследования в формате перечисления "Пока не разбирались", "Будем исправлять". "Не будем исправлять", "Мы считаем что тут правило не применимо"

          Будете вы указанную концепцию применять у себя вручную или автоматически сути не меняет — подразумевается что кодировать нужно согласно патернам в любом случае (именно что вставлять указанный правильный код), за исключением случаев когда команда не берет на себя ответственность за конкретный участок кода где принимает решение правило не применять или не исправлять.


          Исходный посыл статьи — сейчас есть концептуальная проблема (в силу разных причин в зоне компетентности): правило не применяется никогда, пока не упадет продуктив и конкретный участок кода не придется исследовать и переписать. О чем вы в своем комментарии и пишите. Вы просите примеры доказательства проблемы, а как минимум один пример указан в статье — это конкретная боль при расследовании ошибки "в данной транзакции уже происходили ошибки" при поддержке боевой информационной системы.


          Чтобы перейти к конкретным примерам пагубности на примере реального кода, скажем из типовых, нам нужно разделить концептуальный "холивар" и конкретно прикладной. У вас в комментарии они совмещены. Вы спорите с концепцией правил кодирования пытаясь увести разговор в конкретно прикладной код. Я даже знаю что дальше будет:


          • я вам приведу участок/участки из ERP/УНФ где есть такие замечания и мы перейдем к разбору конкретных участков кода
          • мы разберем как бы кодили мы, какое архитектурное решение выбрали и почему, вы выскажете свое архитектурное мнение
          • и в целом на этом холивар можно заканчивать — потому что у нас будут проблемы в концепте: мы будем исходить из того что исправить нужно обязательно и будем искать решение исходя из этого, а вы будете исходить от обратного: как ничего не менять и обосновать это.

          Исходя из этого я вам и пытаюсь донести — стоит разбирать примеры, только если вы согласны с концептом, тогда это будет полезно всем читающим.


          1. grumegargler
            15.08.2018 07:21

            Нет никакой неприязни, для меня ценны только знания, которые я могу извлечь из общения с кем бы то ни было. Простите, что не робею писать о том, что действительно думаю, и выверял на практике. Но у меня есть мнение, очень непопулярное, что стандарт в данном случае — неверный. Это не потому, что я решил выделиться, а потому, что сталкивался с описываемыми проблемами, и наиболее практичным нахожу способ работы с раскруткой транзакций в том месте, где это требуется логикой программы, а вставка попыток — по моему мнению, ничего не решает, потому что достаточно один раз так не сделать, и при описываемом подходе — мы получим те же проблемы. Я допускаю, что не прав, и буду раз на восполнение пробела знаний, но от того, что вы повторите мне “не применяется правило”, “упадет продуктив” и так далее, это, не сердитесь, больше какое-то запугивание, а не четкий ответ.


    1. mayorovp
      15.08.2018 09:05

      На каком основании делается вывод, что ДелаемЧтоТо(); начал и не закончил транзакцию лишь однажды, а если это произошло два или более раз?

      Насколько я понял, защита тут от ровно противоположного — вдруг ДелаемЧтоТо() не начинал транзакцию, но ее закончил.


  1. grumegargler
    14.08.2018 23:54

    НачатьТранзакцию()
    Пока Выборка.Следующий() Цикл
    
        // чтение объекта по ссылке
        // запись объекта
    
    КонецЦикла;
    ЗафиксироватьТранзакцию();


    ввести управляемую блокировку во избежание deadlock
    ввести вызов метода Заблокировать
    обернуть в «попытку», как показано выше

    После управляемой блокировки применять Заблокировать () уже не нужно.


    1. EvilBeaver Автор
      15.08.2018 06:44

      Кто это сказал?


      1. grumegargler
        15.08.2018 06:47

        приведите пожалуйста сценарий на примере кода выше, при котором в методе Заблокировать () понадобится необходимость.


        1. EvilBeaver Автор
          15.08.2018 10:52
          +1

          Привожу.


          Секунда 1. (события внутри секунды произошли в порядке следования)


          • Пользователь открыл форму документа
          • Фоновый поток с вашим кодом установил упр. блокировку и сделал ПолучитьОбъект()
          • Пользователь изменил строчку в форме и тем самым установил пессимистическую блокировку (считай, что вызвал ДокументОбъект.Заблокировать())

          Секунда 2:


          • Ваш фоновый поток не вызывал ДокументОбъект.Заблокировать() — и это ошибка
          • Ваш фоновый поток, проигнорировал занятость объекта и вызвал метод Записать()

          Секунда 3:


          • Пользователь нажал кнопку "Записать", полагая успешную операцию и получил ошибку "Объект изменен или удален" (сработала оптимичтическая блокировка)

          Секунда 28:


          • Пользователь пишет письмо в саппорт с предположением (в данном случае резонным), что программисты мудаки плохо работают.

          Правильный сценарий


          • В секунду №1 ваш код вызывает ДокументОбъект.Заблокировать() сразу после получения объекта. И если не смог заблокировать — отказывается от операции.
          • Пользователь, пытаясь изменить поле формы на вашем уже пессимистически заблокированном объекте получает внятное диагностическое сообщение "Объект А уже редактируется пользователем Х на компьютере Y".

          И теперь, даже если в секунду 28 пользователь пишет письмо в саппорт, то ему можно ответить, что это он не умеет читать сообщения на экране.


          1. grumegargler
            15.08.2018 17:59

            Ясно, что имелось ввиду, спасибо!
            Меня резанула строгость паттерна, и если сочтете нужным, то возможно, имеет смысл смягчить формулировку в статье на счет Заброкировать (), потому что в высоко нагруженных системах, откатывать транзакцию может быть непозволительной роскошью ради забывчивого пользователя (усыпляют компьютеры в режиме редактирования, а с мобильным клиентом, эта проблема происходит чаще, плюс мы сталкивались, что оптимистичная не всегда уходит через 20 минут, а если уходит — то с сообщением Session is not available or has been dropped). Нередко наихудшими, являются последствия невыполнения сложных алгоритмом в фоне, чем выброс объекта пользователя.


            1. EvilBeaver Автор
              15.08.2018 19:08

              В качестве соседнего пользователя может ведь быть и другой фоновый сеанс


              1. grumegargler
                15.08.2018 19:12

                а как, ведь мы наложили (наложим и там тоже) упр.блокировку?


                1. EvilBeaver Автор
                  16.08.2018 10:32

                  И что это меняет?


  1. kuzyara
    15.08.2018 06:37

    Куча вариантов, но ожидал увидеть (с v8std):

    НачатьТранзакцию();
    Попытка
    //здесь формирую и провожу документы программно
     ...
     ЗафикситоватьТранзакцию();
    Исключение
     ОтменитьТранзакцию()//тут ошибка "Транзакция не активна"
    КонецПопытки;

    С одной стороны автор пишет «мы обязаны заботиться только о нашей транзакции», и тут же пытается проверить состояние общего счетчика транзакции «Если ТранзакцияАктивна() Тогда ОтменитьТранзакцию(); ...». Транзакцию какого уровня пытаетесь отменить?

    На самом деле _код_ не так плох, как вам кажется и оптимистичные подходы имеют подавляющее большинство. Количество вызовов ОтменитьТранзакцию/ТранзакцияАктивна в коде… ммм… УТ11 имеет соотношение 504/50, т.е. 10 к 1 (опять же большая часть которых относится к логике _текущего_ контекста).

    Предложенный в статье финальный вариант в общем случае является неверным и имеет смысл применять только как костыль отладки ошибки «В данной транзакции уже ...»


    1. EvilBeaver Автор
      15.08.2018 06:42
      +1

      Предложенный вариант более безопасен с точки зрения логирования фоновых процессов и в тексте даже выделено жирным шрифтом — почему введено условие с проверкой активности


  1. Dream2
    15.08.2018 11:04

    И еще вопрос: где начинать транзакцию? До начала попытки или в начале самой попытки? Есть вообще разница?


    1. EvilBeaver Автор
      15.08.2018 11:10

      В стандарте — до. Я, со своей стороны, разницы не вижу. Хотя допускаю, что она есть. Можем прямо тут и выяснить, кстати говоря. Случай подходящий.


    1. alexey-lustin
      15.08.2018 12:51

      Я кажется знаю ответ — он в java мире описан.

      начало тут docs.oracle.com/javase/tutorial/essential/exceptions/declaring.html

      описание таково:

      * любой метод может быть объявлен как «выбрасывающий исключение»

      public void myMethod() throws Exception {}


      * когда рекомендуется обязательно использовать декларирование метода как выбрасывающего исключение — обязательно в тех случаях когда идет внешняя коммуникация: с файловой системой, сокетами и др.

      тогда при вызове метода программист просто обязан будет обернуть его в попытку — исключение, иначе не скомпилируется.

      отсюда ответ на наш вопрос лежит в плоскости ответа на вопрос

      * НачатьТранзакцию() как метод верхнего уровня коммницирует с чем-нибудь внешним?

      профайлер показывает что — в момент вызовать НачатьТранзакцию() на сервер СУБД НЕ уходит инструкции START TRANSACTION, а значит сетевой коммуникации с сервером СУБД не происходит, а значит мой ответ:

      * нет никакой нужды добавлять эту инструкцию в попытку от слова совсем


      1. EvilBeaver Автор
        15.08.2018 13:24

        нет никакой нужды добавлять эту инструкцию в попытку от слова совсем

        Нет нужды, кроме красивых отступов, про которые писалось выше.


    1. sancoder
      15.08.2018 16:51

      До попытки. Поищите мои комментарии выше.


      1. Dream2
        16.08.2018 11:55

        Спасибо