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С:
- Гранулярность блокировок — всегда указывайте конкретные значения измерений при блокировке регистров, а не блокируйте весь регистр целиком
- Минимизация времени транзакции — выносите тяжёлые вычисления за пределы транзакции
- Использование SNAPSHOT изоляции — включение уровня изоляции READ_COMMITTED_SNAPSHOT в SQL Server значительно снижает конкурентность читателей и писателей
- Разделение потоков записи — использование очередей и фоновых заданий для асинхронной записи
Признак 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