При выполнении распределенной синхронизации данных (РИБ) в клиент-серверном варианте работы на базе MS SQL Server разработчики и системные администраторы нередко сталкиваются с ошибкой: «Ошибка при вызове метода контекста (ЗаписатьИзменения): Конфликт блокировок при выполнении транзакции: Превышено максимальное время ожидания предоставления блокировки». При этом чтение входящего файла обмена может проходить успешно, а в файловом варианте информационной базы та же процедура отрабатывает без сбоев.
Разберем детально, почему возникает данная ошибка, как провести точную диагностику источника блокировки и какими способами можно восстановить стабильную выгрузку данных.
Проанализируем механику работы метода ЗаписатьИзменения(). В момент формирования исходящего сообщения обмена платформа 1С накладывает блокировки на таблицы регистрации изменений (в СУБД они имеют префикс _ChangeRec*) и считывает накопленные данные для выбранного узла. Ошибка ожидания блокировки возникает в следующих случаях:
Рассмотрим первое действие, которое часто решает проблему без перенастройки конфигурации. Если на сервере установлена устаревшая платформа с известными багами управления транзакциями (в частности, релизы 8.3.9.хххх):
Разберем регламентные операции на стороне СУБД, позволяющие устранить неоптимальные планы выполнения запросов:
EXEC sp_updatestats;
-- Или точечно для конкретных таблиц регистрации
UPDATE STATISTICS _ChangeRecXXXX WITH FULLSCAN;
Max Degree of Parallelism = 1, чтобы исключить паразитные взаимные ожидания потоков в запросах 1С.TempDB шаг авторасширения задан в мегабайтах (например, 256–512 МБ), а учетной записи службы SQL Server предоставлены права мгновенной инициализации файлов (Instant File Initialization).Если обновление платформы и обслуживание СУБД не дали мгновенного результата, выясним точного «виновника» блокировки. Для этого настроим сбор событий технологического журнала 1С.
Создадим файл конфигурации logcfg.xml в каталоге настроек сервера 1С:
<config xmlns="http://v8.1c.ru/v8/tech-log">
<log location="C:\LOGS\1C_LOCKS" history="24">
<event>
<eq property="name" value="TLOCK"/>
</event>
<event>
<eq property="name" value="TTIMEOUT"/>
</event>
<event>
<eq property="name" value="TDEADLOCK"/>
</event>
<property name="all"/>
</log>
</config>
В момент повторения ошибки в логах событий TTIMEOUT и TLOCK найдем поля WaitConnections и Context. Это укажет на номер сеанса, исполняемый контекст кода и таблицу, удерживающую ресурс.
Параллельно в MS SQL Server выполним запрос к динамическим представлениям (DMV) для просмотра дерева блокировок в реальном времени:
SELECT
r.session_id AS BlockedSession,
r.blocking_session_id AS BlockingSession,
r.wait_type,
r.wait_time,
t.text AS SQLText
FROM sys.dm_exec_requests r
CROSS APPLY sys.dm_exec_sql_text(r.sql_handle) t
WHERE r.blocking_session_id <> 0;
Посмотрим на программные методы обхода длительных блокировок в самой конфигурации 1С:
ПланыОбмена.УдалитьРегистрациюИзменений(УзелОбмена);
Управляемый вместо Автоматический, что избавит от избыточных разделяемых блокировок страниц на уровне SQL Server.ПланыОбмена.ЗаписатьИзменения() применим поэтапную выгрузку через ПланыОбмена.ВыбратьИзменения() с порционной фиксацией транзакций.Рассмотрим пример порционной обработки данных:
ВыборкаИзменений = ПланыОбмена.ВыбратьИзменения(УзелОбмена, НомерСообщения);
Счетчик = 0;
РазмерПорции = 100;
НачатьТранзакцию();
Пока ВыборкаИзменений.Следующий() Цикл
Объект = ВыборкаИзменений.Получить();
// Сериализация и запись объекта в поток сообщения
ЗаписатьXML(ЗаписьXML, Объект);
Счетчик = Счетчик + 1;
Если Счетчик % РазмерПорции = 0 Тогда
ЗафиксироватьТранзакцию();
НачатьТранзакцию();
КонецЕсли;
КонецЦикла;
Если ТранзакцияАктивна() Тогда
ЗафиксироватьТранзакцию();
КонецЕсли;
Такой подход гарантирует минимальное время удержания транзакционных замков в базе данных и полностью предотвращает возникновение ошибок таймаута блокировок.