Почему падает стандартная обработка регистрации изменений и как правильно удалять регистрацию в планах обмена 1С?

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

Работа с планами обмена в платформе 1С:Предприятие — одна из наиболее ответственных задач разработчика. Ошибки проектирования здесь могут приводить не просто к невыгрузке отдельных документов, но и к серьезным сбоям в работе СУБД, блокировкам таблиц и крашу стандартного интерфейса платформы. В рамках этой статьи мы подробно проанализируем классическую архитектурную ошибку, связанную с некорректным удалением регистрации изменений, выясним причины ее возникновения и рассмотрим правильные паттерны проектирования обменов данными.

Анатомия проблемы: что пошло не так в исходном коде?

Рассмотрим типовой сценарий. Разработчик создает новый план обмена, включает туда документ РеализацияТоваровУслуг (РТУ) с включенной авторегистрацией. Для выгрузки данных пишется следующий код в правилах выборки данных (ПВД):


Узел = ПланыОбмена.НашПланОбмена.НайтиПоКоду("2");
Выборка = ПланыОбмена.ВыбратьИзменения(Узел, 1);
ВыборкаДанных = Новый Массив;
Пока Выборка.Следующий() Цикл
    Данные = Выборка.Получить();
    Если Данные.Метаданные().Имя  = "РеализацияТоваровУслуг" Тогда
        ВыборкаДанных.Добавить(Данные.Ссылка);
        ПланыОбмена.УдалитьРегистрациюИзменений(Узел, Данные.Ссылка);
    КонецЕсли;
КонецЦикла;

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

Ошибка первая: Модификация таблицы изменений внутри цикла выборки

Давайте разберем по шагам, что происходит на уровне СУБД (MS SQL Server или PostgreSQL) при выполнении этого кода. Метод ПланыОбмена.ВыбратьИзменения() создает SQL-курсор (выборку) к таблице регистрации изменений (в физической структуре базы данных она называется _ChangeRec). Этот курсор последовательно считывает записи из таблицы БД.

Когда внутри цикла Пока Выборка.Следующий() мы вызываем метод ПланыОбмена.УдалитьРегистрациюИзменений(Узел, Данные.Ссылка), платформа выполняет SQL-запрос DELETE к той же самой таблице, по которой в данный момент открыт и движется наш активный курсор.

Это приводит к следующим крайне негативным последствиям на уровне СУБД:

  1. Смещение указателя курсора: Удаление текущей (или соседней) строки ломает внутреннюю логику обхода курсора СУБД. В результате часть измененных объектов просто пропускается при выгрузке (платформа «перескакивает» через них, не попадая в выборку).
  2. Эскалация блокировок: Постоянные точечные удаления строк в процессе чтения вызывают наложение монопольных блокировок на строки или целые страницы таблицы изменений. Это мгновенно блокирует работу обычных пользователей, пытающихся провести документы (так как проведение документов при включенной авторегистрации также обращается к таблице _ChangeRec).
  3. Логическое повреждение индексов: Под высокой транзакционной нагрузкой это приводит к тому, что в таблице изменений СУБД появляются записи-сироты с некорректными индексами.

Ошибка вторая: Использование жестко заданного номера сообщения

Вторым слабым местом является вызов ПланыОбмена.ВыбратьИзменения(Узел, 1). Передавая вторым параметром жестко захардкоженное число 1, мы указываем платформе пометить все выбранные изменения номером сообщения 1.

Если в процессе выгрузки (например, при отправке данных по COM-соединению в базу-приемник) произойдет сетевой сбой, транзакция прервется. Часть записей останется помеченной номером 1, часть останется с номером 0. При последующих попытках выгрузки возникнет хаос в нумерации сообщений, из-за чего платформа перестанет корректно определять границы выгруженных данных.

Почему «падает» стандартная обработка регистрации изменений?

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

Типовая обработка «Регистрация изменений для обмена данными» из состава БСП (Библиотеки Стандартных Подсистем) использует для отображения зарегистрированных объектов Динамический список. Запрос этого динамического списка соединяет основную таблицу метаданных (например, таблицу ссылок документов Документ.РеализацияТоваровУслуг) с виртуальной таблицей регистрации изменений.

Из-за некорректного удаления данных прямо в цикле выборки на уровне SQL-таблицы образуются дублирующиеся или некорректно проиндексированные записи. В результате выполнения запроса СУБД возвращает для одной и той же ссылки несколько строк в выборке изменений. Динамический список 1С требует абсолютной уникальности ключевого поля (которым выступает ссылка). Обнаружив дубли ключей, платформа аварийно завершает работу формы с системной ошибкой: «Обнаружены дублирующие ключи в динамическом списке». Пользователь теряет возможность просматривать и редактировать регистрацию.

Решение 1: Пакетное удаление регистрации вне цикла (Быстрое исправление)

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

Давайте перепишем код правил выборки данных (ПВД) правильно. Мы будем накапливать ссылки на успешно обработанные документы в массив, а само удаление регистрации выполним одним пакетным запросом ПОСЛЕ завершения цикла обхода.


Узел = ПланыОбмена.НашПланОбмена.НайтиПоКоду("2");

// Выбираем изменения без фиксации номера сообщения (второй параметр не заполняем)
Выборка = ПланыОбмена.ВыбратьИзменения(Узел);

ВыборкаДанных = Новый Массив;
МассивДляУдаления = Новый Массив;

Пока Выборка.Следующий() Цикл
    Данные = Выборка.Получить();
    Если Данные.Метаданные().Имя = "РеализацияТоваровУслуг" Тогда
        ВыборкаДанных.Добавить(Данные.Ссылка);
        // Накапливаем ссылки в локальной памяти (в массиве)
        МассивДляУдаления.Добавить(Данные.Ссылка);
    КонецЕсли;
КонецЦикла;

// Здесь мы выполняем отправку собранных данных по COM или иным способом.
// Если отправка прошла успешно, удаляем регистрацию пакетно ОДНОЙ транзакцией:
Если МассивДляУдаления.Количество() > 0 Тогда
    ПланыОбмена.УдалитьРегистрациюИзменений(Узел, МассивДляУдаления);
КонецЕсли;

Преимущества этого подхода:

Решение 2: Классический механизм сообщений обмена (Рекомендуемый и надежный)

Давайте рассмотрим, как устроен промышленный стандарт обмена данными в платформе 1С. Платформа имеет встроенный механизм квитирования (подтверждения получения) сообщений с помощью объекта ЗаписьСообщенияОбмена. Этот способ исключает любые ручные манипуляции с массивами ссылок и гарантирует 100% целостность данных.

Проанализируем пошаговый алгоритм реализации такого обмена:


Узел = ПланыОбмена.НашПланОбмена.НайтиПоКоду("2");

// Создаем запись сообщения обмена. Платформа сама определит следующий свободный НомерСообщения.
ЗаписьXML = Новый ЗаписьXML;
ЗаписьXML.УстановитьСтроку(); // Инициализируем запись в память

ЗаписьСообщения = ПланыОбмена.СоздатьЗаписьСообщения();
ЗаписьСообщения.НачатьЗапись(ЗаписьXML, Узел);

// Выбираем изменения, автоматически привязанные к этому Номеру Сообщения
Выборка = ПланыОбмена.ВыбратьИзменения(Узел, ЗаписьСообщения.НомерСообщения);
ВыборкаДанных = Новый Массив;

Пока Выборка.Следующий() Цикл
    Данные = Выборка.Получить();
    Если Данные.Метаданные().Имя = "РеализацияТоваровУслуг" Тогда
        ВыборкаДанных.Добавить(Данные.Ссылка);
    КонецЕсли;
КонецЦикла;

Попытка
    // Выполняем процедуру отправки данных в базу-приемник
    ВыгрузитьДанныеВПриемник(ВыборкаДанных); 
    
    // Если отправка завершилась успешно, закрываем сообщение
    ЗаписьСообщения.ЗакончитьЗапись();
    
    // Удаляем ВСЕ изменения, которые были привязаны к этому номеру сообщения
    ПланыОбмена.УдалитьРегистрациюИзменений(Узел, ЗаписьСообщения.НомерСообщения);
    
Исключение
    // В случае любой ошибки прерываем запись сообщения. 
    // Все изменения останутся зарегистрированными и будут выбраны повторно в следующий раз.
    ЗаписьСообщения.ПрерватьЗапись();
    ЗаписьXML.Закрыть();
    ВызватьИсключение "Ошибка обмена данными: " + ОписаниеОшибки();
КонецПопытки;

При использовании этого механизма платформа 1С берет всю сложную работу на себя:

Как реанимировать базу данных заказчика, если ошибка уже возникла?

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

Давайте выполним следующие шаги по реанимации:

  1. Программная очистка регистрации изменений. Поскольку интерактивно открыть форму мы не можем, напишем простую внешнюю обработку со следующим кодом для полной очистки таблицы изменений по нашему узлу:
    
    Узел = ПланыОбмена.НашПланОбмена.НайтиПоКоду("2");
    ПланыОбмена.УдалитьРегистрациюИзменений(Узел); // Полное удаление регистрации по узлу без фильтров
    
    Это действие полностью очистит таблицу _ChangeRec для данного узла и устранит все логические несоответствия, порожденные старым кодом.
  2. Тестирование и исправление БД. Если после очистки регистрации ошибка в динамическом списке сохраняется (что указывает на физическое повреждение индексов таблицы регистрации изменений), необходимо выполнить обслуживание базы данных. Запустите Конфигуратор, перейдите в меню Администрирование — Тестирование и исправление.... Установите флажки «Проверка логической целостности» и «Проверка ссылочной целостности», выберите режим «Тестирование и исправление» и нажмите кнопку «Выполнить».
  3. Обслуживание СУБД (для клиент-серверных баз). Настоятельно рекомендуется запустить процедуру REINDEX или выполнить обновление статистик (Update Statistics) для таблиц регистрации изменений через MS SQL Server Management Studio или pgAdmin.

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

← На главную