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

Программист 1С v8.3 (Управляемые формы) IT и автоматизация бизнеса
← На главную

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

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

---

1. В чем принципиальная разница между ДокументОбъект.Заблокировать() и БлокировкаДанных?

Разработчики часто путают два принципиально разных механизма блокировки, доступных в платформе: объектную (пессимистическую) и транзакционную (управляемую). Рассмотрим их особенности подробнее.

Объектная (пессимистическая) блокировка: метод ДокументОбъект.Заблокировать()

Этот механизм работает на уровне сеансов платформы «1С» и предназначен главным образом для интерактивной работы. Его цель — не дать двум пользователям одновременно редактировать один и тот же объект через форму. Особенности метода:

Пример использования объектной блокировки:


ДокументОбъект = СсылкаНаДокумент.ПолучитьОбъект();
Попытка
    ДокументОбъект.Заблокировать();
Исключение
    // Обработка ситуации, когда документ уже редактируется другим пользователем
    ВызватьИсключение "Документ заблокирован в другом сеансе: " + ОписаниеОшибки();
КонецПопытки;

Транзакционная блокировка: объект БлокировкаДанных

Этот механизм работает исключительно внутри транзакции и управляется менеджером блокировок платформы «1С» совместно с СУБД. Особенности механизма:

Проанализируем пример установки управляемой транзакционной блокировки:


Блокировка = Новый БлокировкаДанных;
ЭлементБлокировки = Блокировка.Добавить("Документ.РеализацияТоваровУслуг");
ЭлементБлокировки.УстановитьПараметр("Ссылка", СсылкаНаДокумент);
ЭлементБлокировки.Режим = РежимБлокировкиДанных.Исключительный;

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

---

2. Медленная работа УстановитьПометкуУдаления() и подводные камни ручных аналогов

Метод УстановитьПометкуУдаления() работает относительно медленно, так как платформа выполняет под капотом огромный объем работы:

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

Чем опасен "ручной" аналог?

В попытках ускорить процесс разработчики часто прибегают к следующей схеме: вручную очищают движения через наборы записей регистров, устанавливают признак ПометкаУдаления = Истина и записывают объект в режиме обмена данными, чтобы обойти подписки на события:


// Очистка движений
Для Каждого Набор Из ДокументОбъект.Движения Цикл
    Набор.Очистить();
    Набор.Записать();
КонецЦикла;

// Быстрая запись документа без проверок
ДокументОбъект.ПометкаУдаления = Истина;
ДокументОбъект.ОбменДанными.Загрузка = Истина;
ДокументОбъект.Записать();

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

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

---

3. Проблема рассинхронизации ВерсияДанных при откате транзакции

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

  1. Программа получает объект из базы данных: Док = Ссылка.ПолучитьОбъект();. На этот момент объект в памяти имеет актуальную версию данных из СУБД (например, Версия_1).
  2. Внутри транзакции вызывается метод записи объекта. Запись самого документа проходит успешно, СУБД присваивает ему новую версию (например, Версия_2), и платформа обновляет свойство ВерсияДанных внутри переменной Док в оперативной памяти сервера приложений до значения Версия_2.
  3. Далее в этой же транзакции происходит какая-либо ошибка (например, конфликт блокировок при записи движений), из-за чего транзакция откатывается СУБД.
  4. После отката транзакции СУБД возвращает состояние документа в базе данных к исходному (к версии Версия_1).
  5. Но в оперативной памяти сервера 1С переменная Док не откатывается! Она по-прежнему хранит значение свойства ВерсияДанных равным Версия_2.
  6. Если разработчик в блоке Исключение попытается выполнить запись этого же объекта Док (например, чтобы записать лог ошибки в комментарий документа), платформа сравнит версию в памяти (Версия_2) с версией в СУБД (Версия_1). Из-за несовпадения версий сработает механизм оптимистической блокировки, и система выбросит ошибку: "Данные изменены другим пользователем или удалены".

Проанализируем некорректный код, который приводит к этой проблеме:


Док = СсылкаНаДокумент.ПолучитьОбъект();
Попытка
    НачатьТранзакцию();
    Док.УстановитьПометкуУдаления(Истина);
    ЗафиксироватьТранзакцию();
Исключение
    ОтменитьТранзакцию();
    
    // ОШИБКА: Объект "Док" содержит неактуальную версию данных из-за отката транзакции!
    Док.ОбменДанными.Загрузка = Истина;
    Док.Комментарий = "Ошибка обработки: " + ОписаниеОшибки();
    Попытка
        Док.Записать(); // Тут произойдет падение с ошибкой "Данные изменены другим пользователем"
    Исключение
        // Второе исключение
    КонецПопытки;
КонецПопытки;

Правильное решение:

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


Док = СсылкаНаДокумент.ПолучитьОбъект();
Попытка
    НачатьТранзакцию();
    Док.УстановитьПометкуУдаления(Истина);
    ЗафиксироватьТранзакцию();
Исключение
    ОтменитьТранзакцию();
    
    // ПРАВИЛЬНО: Перечитываем объект заново, чтобы синхронизировать ВерсиюДанных с СУБД
    Док = СсылкаНаДокумент.ПолучитьОбъект();
    Док.ОбменДанными.Загрузка = Истина;
    Док.Комментарий = "Ошибка при установке пометки удаления: " + КраткоеПредставлениеОшибки(ИнформацияОбОшибке());
    Попытка
        Док.Записать();
    Исключение
        // Обработка критической ошибки записи лога
    КонецПопытки;
КонецПопытки;

---

4. Реализация паузы в фоновых заданиях 1С

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

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

Способ 1. Встроенный метод ВызватьПаузу() (Платформа 8.3.25 и выше)

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


// Приостанавливаем выполнение текущего потока на 3 секунды
ВызватьПаузу(3000);

Способ 2. Обходной путь через ОжидатьЗавершенияВыполнения() (Для версий ниже 8.3.25)

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


Процедура ВыполнитьПаузуНаСервере(СекундыПаузы) Экспорт
    
    Если СекундыПаузы <= 0 Тогда
        Возврат;
    КонецЕсли;
    
    // Запускаем абсолютно "пустую" процедуру в качестве нового фонового задания
    ФЗ = ФоновыеЗадания.Выполнить("ОбщегоНазначения.ПустаяПроцедура");
    МассивЗаданий = Новый Массив;
    МассивЗаданий.Добавить(ФЗ);
    
    Попытка
        // Метод заставляет текущий серверный поток ожидать завершения ФЗ
        // Передаем таймаут ожидания, равный требуемой паузе
        ФоновыеЗадания.ОжидатьЗавершенияВыполнения(МассивЗаданий, СекундыПаузы);
    Исключение
        // По истечении указанного времени ожидания платформа вызовет исключение,
        // выполнение продолжится дальше, что и дает нам эффект паузы.
    КонецПопытки;
    
КонецПроцедуры

Способ 3. Использование функций БСП и БТС

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

← На главную