Работа с планами обмена в высоконагруженных системах часто сопровождается критическими ошибками, такими как «Конфликт блокировок при выполнении транзакции: Microsoft SQL Server Native Client: Транзакция вызвала взаимоблокировку ресурсов». Проанализируем ситуацию, когда ошибка возникает в методе ВыбратьИзменения, и разберем по шагам, как оптимизировать этот процесс.
Рассмотрим природу механизма планов обмена. Когда мы вызываем метод ПланыОбмена.ВыбратьИзменения(), система не просто считывает данные из таблиц изменений. Она выполняет пишущую операцию: каждой выбранной записи в таблице регистрации изменений присваивается номер исходящего сообщения.
В конфигурациях, работающих в автоматическом режиме блокировок (как в примере автора темы), 1С устанавливает уровень изоляции транзакций SQL SERIALIZABLE. Это самый жесткий уровень изоляции, при котором чтение диапазона данных может заблокировать вставку новых записей в этот диапазон. Проанализируем ситуацию: один процесс пытается зарегистрировать новое изменение (вставить строку в таблицу _ReferenceChngR), а в это же время регламентное задание обмена пытается проставить номера сообщений для существующих записей (выполнить UPDATE). Возникает конфликт, который часто перерастает в дедлок (взаимоблокировку).
Дополнительным фактором является эскалация блокировок. Если объем порции данных велик (например, более 3000 записей), SQL Server может заменить блокировки отдельных строк на блокировку всей таблицы изменений, полностью парализуя работу узла обмена. Для диагностики текущих ожиданий и конфликтов на уровне СУБД можно использовать обработку для анализа запросов и блокировок SQL-сервера.
Одной из эффективных мер является предварительный «захват» данных в транзакции. Выясним причину, почему это помогает: если мы сначала прочитаем данные с исключительной блокировкой, другие процессы не смогут начать конкурирующую операцию записи до завершения нашей транзакции. Это выстраивает процессы в очередь вместо того, чтобы позволить им столкнуться в дедлоке.
Разберем пример реализации этого подхода. Нам необходимо объединить запрос на выборку данных и метод ВыбратьИзменения в один транзакционный блок:
Функция ЗаполнитьСтруктуруИзмененийДляУзла_Оптимизированная(УзелПланаОбмена, ИмяТаблицы)
РезультатФункции = Новый Структура("ДанныеРегистрации, НомерИсходящегоСообщения");
// Начинаем транзакцию для предотвращения дедлоков
НачатьТранзакцию();
Попытка
Запрос = Новый Запрос;
// Используем ключевое слово ДЛЯ ИЗМЕНЕНИЯ
Запрос.Текст = "ВЫБРАТЬ ПЕРВЫЕ 1000
| Изменения.Ссылка КАК Объект
|ИЗ
| Справочник." + ИмяТаблицы + ".Изменения КАК Изменения
|ГДЕ
| Изменения.Узел = &Узел
|ДЛЯ ИЗМЕНЕНИЯ";
Запрос.УстановитьПараметр("Узел", УзелПланаОбмена);
РезультатЗапроса = Запрос.Выполнить();
Если Не РезультатЗапроса.Пустой() Тогда
ДанныеРегистрации = РезультатЗапроса.Выгрузить().ВыгрузитьКолонку("Объект");
ЗаписьСообщения = ПланыОбмена.СоздатьЗаписьСообщения();
ЗаписьXML = Новый ЗаписьXML;
ЗаписьXML.УстановитьСтроку();
ЗаписьСообщения.НачатьЗапись(ЗаписьXML, УзелПланаОбмена);
// Теперь ВыбратьИзменения пройдет гладко, так как записи уже заблокированы нами
ПланыОбмена.ВыбратьИзменения(ЗаписьСообщения.Получатель, ЗаписьСообщения.НомерСообщения, ДанныеРегистрации);
РезультатФункции.Вставить("ДанныеРегистрации", ДанныеРегистрации);
РезультатФункции.Вставить("НомерИсходящегоСообщения", ЗаписьСообщения.НомерСообщения);
ЗаписьСообщения.ЗакончитьЗапись();
КонецЕсли;
ЗафиксироватьТранзакцию();
Исключение
ОтменитьТранзакцию();
ЗаписьЖурналаРегистрации("Обмен", УровеньЖурналаРегистрации.Ошибка, , , КраткоеОписаниеОшибки(ИнформацияОбОшибке()));
ВызватьИсключение;
КонецПопытки;
Возврат РезультатФункции;
КонецФункции
Самый радикальный и правильный способ борьбы с блокировками в 1С 8.3 — это перевод конфигурации (или хотя бы объекта Плана Обмена) в режим управляемых блокировок.
В управляемом режиме 1С использует уровень изоляции Read Committed Snapshot Isolation (на уровне SQL). Это означает, что чтение данных не блокирует их запись. Проанализируем преимущества: процессы регистрации новых изменений документами и процесс выборки изменений для сайта больше не будут конфликтовать на уровне СУБД. Вы сможете выбирать даже большие порции данных без риска получить Deadlock victim.
Посмотрим на ситуацию со стороны объемов данных. Автор темы использует порции по 3000 элементов каждые 3 минуты. Если таблица изменений большая, выполнение ВыбратьИзменения для 3000 записей может длиться несколько секунд, в течение которых таблица удерживается блокировкой.
Рекомендуется выполнить следующие действия:
ВЫБРАТЬ ПЕРВЫЕ 3000 до 500 или 1000. Это сократит время удержания блокировки в одной транзакции.Рассмотрим подробнее механизм очистки изменений, который обсуждался участниками темы. Некоторые разработчики пытаются упростить код и удалять регистрацию сразу после выборки без использования номера сообщения. Проанализируем риски этого подхода:
Если в процессе формирования XML-файла (после выборки, но до удаления) какой-то пользователь изменит объект, это изменение зарегистрируется в плане обмена. Если вы просто удалите регистрацию по списку ссылок, вы «затрете» это новое изменение, и оно никогда не попадет в следующую выгрузку.
Использование номера сообщения гарантирует целостность: метод УдалитьРегистрациюИзменений(Узел, НомерСообщения) удалит только те записи, чей номер сообщения меньше или равен указанному. Все новые изменения, возникшие в процессе обмена, получат пустой номер сообщения и останутся в базе до следующего сеанса.
Если штатные планы обмена продолжают вызывать проблемы, рассмотрим архитектурную альтернативу — использование Регистра Сведений (РС) в качестве очереди задач.
Объект и ресурсом ДатаРегистрации (или ИдентификаторПакета).Этот метод более гибкий, так как вы имеете полный контроль над индексами таблицы и уровнями блокировок при чтении/записи, что часто помогает обойти ограничения стандартного механизма 1С.