5 признаков деградации производительности 1С

5 признаков деградации производительности 1С

Почему производительность 1С деградирует незаметно

Большинство проблем с производительностью 1С не возникают внезапно — они накапливаются месяцами. Система работает, пользователи жалуются на «небольшие подтормаживания», администраторы перезагружают сервер и считают проблему решённой. Тем временем под капотом продолжается медленное разрушение: фрагментируются индексы, накапливаются мёртвые блокировки, планы запросов деградируют, а статистика устаревает.

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

Важно понимать: речь идёт именно о деградации — постепенном ухудшении показателей на фоне неизменной нагрузки. Если вчера проведение документа занимало 2 секунды, а сегодня — 8 секунд при том же числе пользователей и том же объёме данных, это классический сценарий деградации.

Признак 1. Фрагментация индексов и устаревшая статистика

Как это проявляется

Первый и самый распространённый признак — запросы, которые раньше выполнялись за доли секунды, начинают занимать секунды. При этом план выполнения запроса в SQL Server Management Studio показывает операции Index Scan там, где раньше был Index Seek. Это классический симптом фрагментации индексов.

В базах 1С таблицы документов и регистров накапливают тысячи операций вставки и удаления ежедневно. Каждая такая операция может приводить к расщеплению страниц индекса (page split). Через 3–6 месяцев активной работы фрагментация индексов нередко достигает 70–90%, что означает: SQL Server вынужден читать в 3–10 раз больше страниц данных для выполнения того же запроса.

Диагностика через SQL

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

-- Анализ фрагментации индексов базы данных 1С
SELECT 
    OBJECT_NAME(ips.object_id) AS TableName,
    i.name AS IndexName,
    ips.index_type_desc AS IndexType,
    ROUND(ips.avg_fragmentation_in_percent, 2) AS FragmentationPct,
    ips.page_count AS PageCount,
    ips.record_count AS RecordCount
FROM sys.dm_db_index_physical_stats(
    DB_ID(), NULL, NULL, NULL, 'LIMITED'
) AS ips
INNER JOIN sys.indexes i 
    ON ips.object_id = i.object_id 
    AND ips.index_id = i.index_id
WHERE 
    ips.avg_fragmentation_in_percent > 30  -- порог критической фрагментации
    AND ips.page_count > 1000              -- только значимые индексы
ORDER BY 
    ips.avg_fragmentation_in_percent DESC;

Если запрос возвращает десятки строк с фрагментацией выше 50% — ваша система находится в зоне риска. Регулярное обслуживание индексов должно быть настроено как задание SQL Server Agent.

Устаревшая статистика: скрытая угроза

Не менее опасна устаревшая статистика. SQL Server использует статистику для выбора оптимального плана выполнения запроса. Если статистика устарела, оптимизатор может выбрать план с вложенными циклами вместо хэш-соединения, что при больших объёмах данных даёт разницу в производительности в 100 и более раз.

-- Поиск устаревшей статистики в базе 1С
SELECT 
    OBJECT_NAME(s.object_id) AS TableName,
    s.name AS StatName,
    sp.last_updated AS LastUpdated,
    sp.rows AS RowCount,
    sp.rows_sampled AS RowsSampled,
    ROUND(100.0 * sp.rows_sampled / NULLIF(sp.rows, 0), 2) AS SamplePct,
    sp.modification_counter AS Modifications
FROM sys.stats s
CROSS APPLY sys.dm_db_stats_properties(s.object_id, s.stats_id) sp
WHERE 
    OBJECT_NAME(s.object_id) LIKE '_InfoRg%'  -- регистры сведений
    OR OBJECT_NAME(s.object_id) LIKE '_AccumRg%'  -- регистры накопления
ORDER BY 
    sp.modification_counter DESC;

Признак 2. Эскалация блокировок и взаимоблокировки (deadlocks)

Анатомия блокировок в 1С

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

Признаки деградации из-за блокировок:

  • Пользователи периодически получают сообщение «Конфликт блокировок при выполнении транзакции»
  • В журнале регистрации появляются события типа «Ошибка СУБД» с кодом deadlock
  • Время ожидания при проведении документов резко возрастает в часы пик
  • Технологический журнал фиксирует события TLOCK длительностью более 5 секунд

Диагностика блокировок через технологический журнал

Настройте сбор технологического журнала для анализа блокировок. Создайте файл конфигурации logcfg.xml:

<?xml version="1.0" encoding="UTF-8"?>
<config xmlns="http://v8.1c.ru/v8/tech-log">
    <log location="C:\TechLog\Locks" history="1">
        <event>
            <eq property="Name" value="TLOCK"/>
            <gt property="Duration" value="5000"/>
        </event>
        <event>
            <eq property="Name" value="DEADLOCK"/>
        </event>
        <property name="all"/>
    </log>
</config>

Код 1С для анализа конкурентного доступа

Следующий фрагмент кода позволяет проанализировать, какие объекты чаще всего становятся источниками конфликтов блокировок:

// Анализ конкурентного доступа к регистру накопления
// Используется для выявления узких мест при проведении документов

Процедура АнализКонкурентногоДоступа() Экспорт
    
    // Устанавливаем управляемый режим блокировок
    УстановитьРежимБлокировкиДанных(РежимБлокировкиДанных.Управляемый);
    
    НачатьТранзакцию();
    
    Попытка
        // Явная блокировка регистра для анализа
        БлокировкаДанных = Новый БлокировкаДанных();
        
        ЭлементБлокировки = БлокировкаДанных.Добавить(
            "РегистрНакопления.ТоварыНаСкладах"
        );
        
        // Блокируем только нужный склад, а не весь регистр
        ЭлементБлокировки.УстановитьЗначение("Склад", ПараметрыСеанса.ТекущийСклад);
        ЭлементБлокировки.Режим = РежимБлокировкиДанных.Исключительный;
        
        БлокировкаДанных.Заблокировать();
        
        // Выполняем операции с данными
        // ...
        
        ЗафиксироватьТранзакцию();
        
    Исключение
        ОтменитьТранзакцию();
        
        // Логируем информацию о конфликте
        ТекстОшибки = ОписаниеОшибки();
        
        Если СтрНайти(ТекстОшибки, "Конфликт блокировок") > 0 Тогда
            // Записываем в регистр сведений для последующего анализа
            ЗаписьРегистра = РегистрыСведений.ЖурналБлокировок.СоздатьМенеджерЗаписи();
            ЗаписьРегистра.Период = ТекущаяДата();
            ЗаписьРегистра.ОбъектБлокировки = "ТоварыНаСкладах";
            ЗаписьРегистра.ТекстОшибки = ТекстОшибки;
            ЗаписьРегистра.Пользователь = ПользователиИнформационнойБазы.ТекущийПользователь();
            ЗаписьРегистра.Записать();
        КонецЕсли;
        
        ВызватьИсключение;
    КонецПопытки;
    
КонецПроцедуры

Стратегии снижения блокировок

Ключевые подходы к снижению конкурентности блокировок в 1С:

  1. Гранулярность блокировок — всегда указывайте конкретные значения измерений при блокировке регистров, а не блокируйте весь регистр целиком
  2. Минимизация времени транзакции — выносите тяжёлые вычисления за пределы транзакции
  3. Использование SNAPSHOT изоляции — включение уровня изоляции READ_COMMITTED_SNAPSHOT в SQL Server значительно снижает конкурентность читателей и писателей
  4. Разделение потоков записи — использование очередей и фоновых заданий для асинхронной записи

Признак 3. Деградация планов выполнения запросов

Что такое plan cache poisoning

SQL Server кэширует планы выполнения запросов для повторного использования. В 1С все запросы к базе данных параметризованы, что в теории должно обеспечивать эффективное переиспользование планов. Однако на практике возникает явление plan cache poisoning — кэш заполняется неоптимальными планами, составленными для нетипичных распределений данных.

Классический пример: запрос к регистру накопления по остаткам товара был скомпилирован в момент, когда на складе было 5 позиций. Оптимизатор выбрал план с вложенными циклами. Теперь на складе 500 000 позиций, но план остался прежним — с катастрофической производительностью.

Диагностика плохих планов

-- Поиск запросов с наибольшим временем выполнения в кэше планов
-- Адаптировано для баз данных 1С
SELECT TOP 20
    qs.total_elapsed_time / qs.execution_count / 1000 AS AvgDurationMs,
    qs.total_elapsed_time / 1000 AS TotalDurationMs,
    qs.execution_count AS ExecCount,
    qs.total_logical_reads / qs.execution_count AS AvgLogicalReads,
    SUBSTRING(
        st.text, 
        (qs.statement_start_offset / 2) + 1,
        ((CASE qs.statement_end_offset 
            WHEN -1 THEN DATALENGTH(st.text) 
            ELSE qs.statement_end_offset 
          END - qs.statement_start_offset) / 2) + 1
    ) AS QueryText,
    qp.query_plan AS ExecutionPlan
FROM sys.dm_exec_query_stats qs
CROSS APPLY sys.dm_exec_sql_text(qs.sql_handle) st
CROSS APPLY sys.dm_exec_query_plan(qs.plan_handle) qp
WHERE 
    st.text LIKE '%_AccumRg%'  -- фильтр по таблицам 1С
    OR st.text LIKE '%_Document%'
ORDER BY 
    qs.total_elapsed_time / qs.execution_count DESC;

Оптимизация запросов на стороне 1С

Рассмотрим типичный антипаттерн — запрос с условием на виртуальную таблицу без параметров:

// ПЛОХО: условие наложено после получения виртуальной таблицы
// SQL Server вынужден сначала свернуть весь регистр, потом отфильтровать
Запрос = Новый Запрос;
Запрос.Текст = 
    "ВЫБРАТЬ
    |    Остатки.Номенклатура,
    |    Остатки.КоличествоОстаток
    |ИЗ
    |    РегистрНакопления.ТоварыНаСкладах.Остатки КАК Остатки
    |ГДЕ
    |    Остатки.Склад = &Склад
    |    И Остатки.Номенклатура В (&СписокНоменклатуры)";

Запрос.УстановитьПараметр("Склад", ВыбранныйСклад);
Запрос.УстановитьПараметр("СписокНоменклатуры", СписокНоменклатуры);

// ХОРОШО: параметры переданы в виртуальную таблицу
// SQL Server оптимизирует свёртку регистра с учётом фильтров
Запрос = Новый Запрос;
Запрос.Текст = 
    "ВЫБРАТЬ
    |    Остатки.Номенклатура,
    |    Остатки.КоличествоОстаток
    |ИЗ
    |    РегистрНакопления.ТоварыНаСкладах.Остатки(
    |        &Период,
    |        Склад = &Склад
    |            И Номенклатура В (&СписокНоменклатуры)
    |    ) КАК Остатки";

Запрос.УстановитьПараметр("Период", КонецДня(РабочаяДата));
Запрос.УстановитьПараметр("Склад", ВыбранныйСклад);
Запрос.УстановитьПараметр("СписокНоменклатуры", СписокНоменклатуры);

Разница в производительности между этими двумя вариантами может составлять 10–50 раз на больших объёмах данных. Первый вариант заставляет SQL Server выполнить полное сканирование таблицы итогов регистра, второй — использует индекс с предикатом.

Признак 4. Рост числа активных сессий и ожиданий ввода-вывода

Метрики ожиданий как индикатор здоровья системы

SQL Server ведёт детальную статистику ожиданий — времени, которое потоки проводят в ожидании различных ресурсов. Анализ типов ожиданий (wait types) — один из самых мощных инструментов диагностики деградации производительности.

Наиболее критичные типы ожиданий для систем 1С:

Тип ожиданияЧто означаетПорог тревоги
PAGEIOLATCH_SH/EXОжидание чтения страниц с диска> 10 мс среднее
LCK_M_XОжидание эксклюзивной блокировки> 1% от времени
WRITELOGОжидание записи в лог транзакций> 5 мс среднее
CXPACKETПараллелизм, дисбаланс потоков> 20% от времени
SOS_SCHEDULER_YIELDДавление на CPU> 5% от времени

Запрос для анализа ожиданий

-- Топ ожиданий SQL Server за время работы экземпляра
-- Исключаем системные ожидания, не влияющие на производительность
SELECT TOP 15
    wait_type AS WaitType,
    waiting_tasks_count AS WaitCount,
    wait_time_ms / 1000.0 AS WaitTimeSec,
    ROUND(
        100.0 * wait_time_ms / SUM(wait_time_ms) OVER(), 
        2
    ) AS WaitPct,
    ROUND(
        1.0 * wait_time_ms / NULLIF(waiting_tasks_count, 0), 
        2
    ) AS AvgWaitMs
FROM sys.dm_os_wait_stats
WHERE wait_type NOT IN (
    -- Исключаем фоновые системные ожидания
    'SLEEP_TASK', 'BROKER_TO_FLUSH', 'BROKER_TASK_STOP',
    'CLR_AUTO_EVENT', 'DISPATCHER_QUEUE_SEMAPHORE',
    'FT_IFTS_SCHEDULER_IDLE_WAIT', 'HADR_FILESTREAM_IOMGR_IOCOMPLETION',
    'HADR_WORK_QUEUE', 'LAZYWRITER_SLEEP', 'LOGMGR_QUEUE',
    'ONDEMAND_TASK_QUEUE', 'REQUEST_FOR_DEADLOCK_MONITOR',
    'RESOURCE_QUEUE', 'SERVER_IDLE_CHECK', 'SLEEP_DBSTARTUP',
    'SLEEP_DCOMSTARTUP', 'SLEEP_MASTERDBREADY', 'SLEEP_MASTERMDREADY',
    'SLEEP_MASTERUPGRADED', 'SLEEP_MSDBSTARTUP', 'SLEEP_TEMPDBSTARTUP',
    'SNI_HTTP_ACCEPT', 'SP_SERVER_DIAGNOSTICS_SLEEP',
    'SQLTRACE_BUFFER_FLUSH', 'WAITFOR', 'XE_DISPATCHER_WAIT',
    'XE_TIMER_EVENT', 'BROKER_EVENTHANDLER', 'CHECKPOINT_QUEUE',
    'DBMIRROR_EVENTS_QUEUE', 'SQLTRACE_INCREMENTAL_FLUSH_SLEEP'
)
ORDER BY wait_time_ms DESC;

Мониторинг активных сессий 1С

Для мониторинга активных сессий непосредственно из 1С можно использовать COM-соединение с агентом сервера:

Найдите специалиста для решения этой задачи на koderion.ru