При реализации механизмов обмена данными в 1С:Предприятие 8 разработчики часто сталкиваются с ситуацией, когда вызов метода ПланыОбмена.ВыбратьИзменения() приводит к длительным блокировкам таблиц. Пользователи начинают жаловаться на невозможность записать документы или справочники, возникают ошибки "Конфликт блокировок при выполнении транзакции". Особенно остро эта проблема проявляется при интеграции через Веб-сервисы (Web-services) или HTTP-сервисы. Рассмотрим подробно, почему это происходит и как оптимизировать процесс обмена.
Для начала проанализируем, что именно делает платформа в момент вызова ВыбратьИзменения(Узел, НомерСообщения). Этот метод выполняет две критически важные операции:
_DocumentChngR, _ReferenceChngR и другие) и выбирает ссылки на объекты, зарегистрированные для конкретного узла.NULL (то есть изменения еще ни разу не включались в пакет), проставляется переданный в метод НомерСообщения.С точки зрения СУБД, вторая операция является командой UPDATE. Для выполнения обновления строк платформа обязана наложить эксклюзивную блокировку на эти строки. Если объем данных велик (тысячи объектов), 1С или СУБД могут выполнить эскалацию блокировки, закрыв доступ не к отдельным строкам, а ко всей таблице регистрации изменений целиком. Пока эта транзакция не завершится, любой процесс в системе, пытающийся записать объект, входящий в состав плана обмена, будет стоять в очереди и в итоге может "отвалиться" по таймауту.
Рассмотрим ситуацию, типичную для обмена через веб-сервисы. Проанализируем настройки метаданных. У каждой операции веб-сервиса в конфигураторе есть свойство "В транзакции". Если эта галочка установлена, то вся логика, написанная внутри функции-обработчика веб-сервиса, выполняется в рамках одной глобальной транзакции.
Выясним, к чему это приводит:
ВыбратьИзменения() выполняется в самом начале выгрузки. Он мгновенно накладывает блокировки на таблицы регистрации, чтобы проставить номера сообщений.Решение №1: Самый простой и эффективный способ — зайти в свойства операции вашего веб-сервиса и снять флаг "В транзакции". В этом случае метод ВыбратьИзменения() создаст свою собственную, очень короткую внутреннюю транзакцию, быстро обновит номера и тут же отпустит блокировки, позволяя пользователям работать дальше, пока код занимается подготовкой данных.
Если вы не уверены, открыта ли транзакция в данный момент, мы можем использовать метод ТранзакцияАктивна(). Посмотрим на пример кода, который поможет продиагностировать проблему:
Если ТранзакцияАктивна() Тогда
ЗаписьЖурналаРегистрации("ОбменДанными", УровеньЖурналаРегистрации.Предупреждение, , , "Внимание! Выбор изменений запущен внутри внешней транзакции!");
КонецЕсли;
ВыборкаИзмененных = ПланыОбмена.ВыбратьИзменения(Узел, НомерОтправленного);
Помните, что ВыбратьИзменения() всегда должен работать максимально автономно. Если вам необходимо выполнять выгрузку именно в транзакции (что бывает редко), старайтесь максимально сократить время между выбором изменений и завершением транзакции.
Если стандартный метод ВыбратьИзменения() всё равно вызывает проблемы (например, из-за специфики СУБД или огромных объемов), мы можем реализовать логику "вручную". Рассмотрим алгоритм, который позволяет избежать блокировок методом проставления номеров:
ВыбратьИзменения() используем обычный запрос к таблице изменений. В запросе выбираем только те записи, у которых НомерСообщения IS NULL.ПланыОбмена.УдалитьРегистрациюИзменений(Узел, НомерПринятого).Посмотрим на пример кода для удаления регистрации:
// Предположим, мы получили подтверждение от удаленного узла
// НомерПринятого — это номер сообщения, которое успешно загрузила та сторона
ПланыОбмена.УдалитьРегистрациюИзменений(Узел, НомерПринятого);
Важное замечание: Если вы не вызывали ВыбратьИзменения(), то у ваших объектов в таблице изменений номер сообщения остался NULL. В этом случае УдалитьРегистрациюИзменений их не удалит, так как этот метод удаляет только записи с номером сообщения меньшим или равным указанному. Поэтому использование "ручного" запроса требует аккуратности: либо вы все-таки вызываете ВыбратьИзменения() коротким импульсом, либо используете удаление по конкретным ссылкам.
Существует еще один подход, который иногда применяется в типовых конфигурациях. Мы можем быстро "пробежать" по выборке, чтобы зафиксировать номера, и сразу закрыть это соединение. Рассмотрим пример:
ВыборкаИзмененных = ПланыОбмена.ВыбратьИзменения(Узел, НомерОтправленного);
// Просто обходим выборку, ничего не делая, чтобы принудительно зафиксировать номера
Пока ВыборкаИзмененных.Следующий() Цикл
// Пустой цикл
КонецЦикла;
// После этого номера проставлены. Если внешней транзакции нет, блокировки сняты.
// Теперь мы можем спокойно выбирать данные запросом по НомеруСообщения
Однако, как показывает практика, если флаг "В транзакции" у веб-сервиса включен, даже такой обход не спасет от блокировок до конца работы всего сервиса. Поэтому приоритетным решением остается настройка свойств сервиса.
Для правильной организации процесса обмена рекомендуем использовать специализированный объект ЗаписьСообщенияОбмена. Рассмотрим, как он помогает структурировать код:
ЗаписьСообщения = ПланыОбмена.СоздатьЗаписьСообщения();
ЗаписьXML = Новый ЗаписьXML;
ЗаписьXML.ОткрытьФайл(ПутьКФайлу);
ЗаписьСообщения.НачатьЗапись(ЗаписьXML, Узел);
// В этот момент ЗаписьСообщения.НомерСообщения уже содержит следующий номер
Выборка = ПланыОбмена.ВыбратьИзменения(Узел, ЗаписьСообщения.НомерСообщения);
Пока Выборка.Следующий() Цикл
Данные = Выборка.Получить();
ЗаписатьДанные(ЗаписьXML, Данные);
КонецЦикла;
ЗаписьСообщения.ЗакончитьЗапись();
ЗаписьXML.Закрыть();
Использование ЗаписьСообщенияОбмена гарантирует, что заголовок сообщения будет сформирован корректно, а номера сообщений в узле плана обмена обновятся синхронно с регистрацией изменений. Но опять же, помните: весь этот блок не должен находиться внутри долгой транзакции веб-сервиса.
Проанализировав проблему блокировок при выборе изменений, мы можем составить следующий чек-лист для оптимизации обмена:
ВыбратьИзменения() в явные блоки НачатьТранзакцию() ... ЗафиксироватьТранзакцию(), если внутри них выполняется длительная логика.Deadlock при одновременном старте нескольких сессий обмена.Соблюдение этих правил позволит сделать обмен данными в 1С быстрым, стабильным и незаметным для конечных пользователей системы.