Почему периодически зависает поиск по регистру сведений и как это исправить?

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

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

Выясним причины периодических зависаний

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

  1. Неактуальная статистика и эффект Parameter Sniffing на стороне СУБД. В процессе активной круглосуточной работы данные в регистре накапливаются и видоизменяются. Если статистика распределения значений в индексах устаревает, оптимизатор запросов СУБД (в частности, MS SQL Server или PostgreSQL) может внезапно перестроить и закешировать неоптимальный план выполнения. При передаче нетипичного значения параметра (например, пустой строки или самого частотного значения) СУБД вместо быстрого точечного поиска по индексу (Index Seek) выбирает полное сканирование таблицы (Index Scan или Table Scan). Запрос начинает считывать миллионы строк с диска, вызывая длительный «висяк».
  2. Эскалация блокировок (Lock Escalation) на уровне СУБД. Если параллельно с поиском штрихкода в регистр выполняется массивная пакетная запись (регулярный обмен данными с сайтом, загрузка прайс-листов или пересчет остатков), СУБД при достижении критического порога измененных строк автоматически повышает уровень построчных блокировок до блокировки всей таблицы целиком. В этот промежуток времени любая попытка прочитать даже одну запись из таблицы регистра встает в глухую очередь ожидания до фиксации транзакции записи.
  3. Неявное отключение использования индекса платформой или СУБД. Если в метаданных поле штрихкода задано с фиксированной длиной (например, Строка(13)), а в поисковом запросе передается строка переменной длины без дополнения пробелами, СУБД может неявно вызвать скалярную функцию усечения или преобразования к каждому значению в таблице. Это полностью исключает использование индекса. Аналогичный эффект вызывает неосторожное использование условия ПОДОБНО '%...'.
  4. Ошибки в коде 1С и транзакционный контекст. Поиск может выполняться внутри незафиксированной транзакции записи другого объекта (например, документа), из-за чего накладываются избыточные разделяемые или исключительные блокировки. Также пагубно влияет использование устаревшего метода Выбрать(...) с отбором вместо прямого оптимизированного запроса: платформа может считывать избыточный диапазон записей и нагружать оперативную память.

Решение 1. Настройка регламентного обслуживания СУБД

Рассмотрим первоочередной шаг, который решает проблему устаревания планов запросов. Если статистика по индексам не обновляется вовремя, СУБД «слепнет» и строит катастрофически неверные планы. Настроим регулярные задачи обслуживания на сервере баз данных:

  1. Еженощное обновление статистики. Настройте регламентный план обслуживания (Maintenance Plan или скрипт с использованием процедуры sp_updatestats / UPDATE STATISTICS с достаточным процентом сканирования в MS SQL Server или ANALYZE в PostgreSQL). Актуальная статистика гарантирует, что оптимизатор выберет поиск по индексу для точечных запросов.
  2. Реорганизация и дефрагментация индексов. При непрерывной круглосуточной записи индексы подвержены сильной фрагментации. Настройте еженедельное перестроение индексов (Rebuild Index) для таблиц с фрагментацией выше 30% и реорганизацию (Reorganize Index) при фрагментации от 10% до 30%.

Решение 2. Программная диагностика через Технологический журнал и СУБД

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

Создадим файл конфигурации logcfg.xml с фильтром по длительным событиям (например, свыше 10 секунд, что соответствует значению duration > 10000000 в микросекундах):

  1. События TLOCKS и TTIMEOUT — зафиксируют факт ожидания на управляемых блокировках 1С, покажут длительность простоя и конкретное фоновое или пользовательское соединение, удерживающее ресурс.
  2. События DBMSSQL или DBPOSTGRS — сохранят фактический SQL-текст запроса, поступившего в СУБД, физическое имя таблицы, время выполнения и план запроса.
  3. Свойство Context — выведет точную строчку кода модуля 1С, на которой система зависла в ожидании данных.

Параллельно проанализируем состояние на стороне СУБД в момент зависания. В MS SQL Server для этого используем системные представления sys.dm_os_waiting_tasks и sys.dm_exec_requests. Если в колонке типа ожидания фиксируются значения LCK_* — мы имеем дело с эскалацией блокировок от параллельной записи. Если преобладают ожидания PAGEIOLATCH_* или CXPACKET — СУБД выполняет «тяжелое» сканирование таблицы из-за сбившегося плана запроса.

Решение 3. Оптимизация программного кода поиска в 1С

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


Запрос = Новый Запрос;
Запрос.Текст = 
    "ВЫБРАТЬ ПЕРВЫЕ 1
    |	Штрихкоды.Номенклатура КАК Номенклатура,
    |	Штрихкоды.Характеристика КАК Характеристика
    |ИЗ
    |	РегистрСведений.Штрихкоды КАК Штрихкоды
    |ГДЕ
    |	Штрихкоды.Штрихкод = &Штрихкод";

Запрос.УстановитьПараметр("Штрихкод", СокрЛП(ИскомыйШтрихкод));
Результат = Запрос.Выполнить();

Если Не Результат.Пустой() Тогда
    Выборка = Результат.Выбрать();
    Выборка.Следующий();
    // Обработка полученных данных
КонецЕсли;

Обратим внимание на следующие критически важные правила в коде:

  1. Ограничение первой записью. Конструкция ВЫБРАТЬ ПЕРВЫЕ 1 позволяет СУБД прервать сканирование индекса сразу после обнаружения первого совпадения, не перебирая оставшиеся записи таблицы.
  2. Нормализация параметров. Использование функции СокрЛП() исключает передачу случайных пробелов и предотвращает неявное приведение типов при сравнении строковых полей.
  3. Изоляция от транзакций. Проверьте, чтобы код поиска по регистру штрихкодов выполнялся строго до начала транзакций записи документов или справочников, чтобы разделяемые блокировки на чтение не вступали в конфликт с параллельными процессами.

Решение 4. Отключение эскалации блокировок и архитектурные доработки

Если анализ Технологического журнала показал, что зависания вызваны блокировками от фоновых пакетных загрузок в этот же регистр, рассмотрим точечную корректировку на уровне базы данных:

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

    ALTER TABLE dbo._InfoRg12345 SET (LOCK_ESCALATION = DISABLE);

    Это позволит фоновому обмену блокировать только модифицируемые строки, оставляя остальную часть таблицы доступной для поиска.
  2. Корректировка структуры регистра. Посмотрим на состав регистра сведений. Если поле штрихкода создано как обычный реквизит (пусть даже с установленным свойством «Индексировать»), эффективнее вынести его в первое измерение регистра. В таком случае платформа строит кластерный индекс таблицы физически по штрихкоду. Это дает возможность использовать самый быстрый платформенный метод прямого точечного чтения:
    
    ЗначенияОтбора = Новый Структура("Штрихкод", СокрЛП(ИскомыйШтрихкод));
    ДанныеЗаписи = РегистрыСведений.Штрихкоды.Получить(ЗначенияОтбора);
    
    Такой метод генерирует предельно простой SQL-запрос, который обрабатывается СУБД с минимальными накладными расходами и исключает вероятность построения некорректного плана выполнения.
← На главную