Как устранить задержки при выполнении операции INSERT в 1С на SQL Server?

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

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

Анализ ситуации и первичная диагностика

Рассмотрим сценарий: при загрузке данных роботом запись договора или заказа занимает от 100 до 300 мс, хотя по логике это должна быть мгновенная операция. Проверка через SQL Profiler подтверждает: длительность (duration) команды INSERT колеблется в широком диапазоне, в то время как в SQL Server Management Studio (SSMS) аналогичный exec sp_executesql отрабатывает за 3-5 мс. Разберем по шагам, на что нужно обратить внимание в первую очередь.

Во-первых, проанализируем план запроса. Если план состоит из единственного блока Clustered Index Insert с Cost: 100%, это означает, что SQL Server выполняет прямое добавление записи в кластерный индекс таблицы. Отсутствие триггеров и констрейнтов в типовых таблицах 1С подтверждает, что проблема кроется глубже — на уровне системных ресурсов или настроек сервера.

Использование системных представлений для поиска блокировок

Для понимания того, чем занят процесс во время записи, воспользуемся мощным инструментом sp_whoisactive. Эта процедура позволяет в реальном времени увидеть, какие ожидания (waits) возникают у конкретной сессии. Если мы видим ожидание типа PAGEIOLATCH_EX, это прямой сигнал о проблемах взаимодействия между оперативной памятью и дисковой подсистемой.

Для глобального анализа ситуации на сервере проанализируем статистику ожиданий с помощью следующего скрипта, который агрегирует данные из системного представления sys.dm_os_wait_stats:


WITH [Waits] AS
    (SELECT
        [wait_type],
        [wait_time_ms] / 1000.0 AS [WaitS],
        ([wait_time_ms] - [signal_wait_time_ms]) / 1000.0 AS [ResourceS],
        [signal_wait_time_ms] / 1000.0 AS [SignalS],
        [waiting_tasks_count] AS [WaitCount],
        100.0 * [wait_time_ms] / SUM ([wait_time_ms]) OVER() AS [Percentage],
        ROW_NUMBER() OVER(ORDER BY [wait_time_ms] DESC) AS [RowNum]
    FROM sys.dm_os_wait_stats
    WHERE [wait_type] NOT IN (
        N'BROKER_EVENTHANDLER', N'BROKER_RECEIVE_WAITFOR', N'CHECKPOINT_QUEUE',
        N'CLR_AUTO_EVENT', N'CXCONSUMER', N'DIRTY_PAGE_POLL', N'HADR_TIMER_TASK',
        N'LAZYWRITER_SLEEP', N'LOGMGR_QUEUE', N'SERVER_IDLE_CHECK', N'SLEEP_TASK',
        N'SQLTRACE_BUFFER_FLUSH', N'XE_TIMER_EVENT', N'REQUEST_FOR_DEADLOCK_SEARCH'
        )
    AND [waiting_tasks_count] > 0
    )
SELECT
    MAX ([W1].[wait_type]) AS [WaitType],
    CAST (MAX ([W1].[WaitS]) AS DECIMAL (16,2)) AS [Wait_S],
    MAX ([W1].[WaitCount]) AS [WaitCount],
    CAST (MAX ([W1].[Percentage]) AS DECIMAL (5,2)) AS [Percentage],
    CAST ((MAX ([W1].[WaitS]) / MAX ([W1].[WaitCount])) AS DECIMAL (16,4)) AS [AvgWait_S]
FROM [Waits] AS [W1]
INNER JOIN [Waits] AS [W2] ON [W2].[RowNum] <= [W1].[RowNum]
GROUP BY [W1].[RowNum]
HAVING SUM ([W2].[Percentage]) - MAX( [W1].[Percentage] ) < 95;

Разберем результаты выполнения этого запроса. Если в топе находятся ожидания CXPACKET и PAGEIOLATCH_EX, это указывает на две проблемы: избыточный параллелизм и задержки чтения данных с диска в буферный кэш.

Настройка параллелизма (MAXDOP) для 1С

Параметр Max Degree of Parallelism (MAXDOP) определяет максимальное количество ядер процессора, задействованных для выполнения одного запроса. Для баз данных 1С:Предприятие эксперты настоятельно рекомендуют устанавливать это значение в 1. Почему это важно?

Многие запросы 1С построены таким образом, что попытка SQL Server распараллелить их приводит к дополнительным накладным расходам на синхронизацию потоков. Это и вызывает ожидание CXPACKET. В контексте операции INSERT избыточный параллелизм может косвенно замедлять систему, отнимая ресурсы у дисковой подсистемы для обслуживания других параллельных запросов.

Проверим текущую настройку и, если она отлична от 1, изменим её:


EXEC sys.sp_configure N'show advanced options', N'1';
RECONFIGURE;
EXEC sys.sp_configure N'max degree of parallelism', N'1';
RECONFIGURE;

Проблема "разрывов страниц" (Page Splits) при использовании GUID

Одной из скрытых причин медленной вставки в 1С является природа идентификаторов _IDRRef. 1С генерирует GUID (тип binary(16)) не последовательно. Поскольку поле _IDRRef почти всегда является кластерным индексом, SQL Server обязан вставлять новую запись в строго определенное место на диске, чтобы сохранить физический порядок.

Проанализируем механику процесса: когда страница данных (объемом 8 КБ) заполнена, а новая запись должна попасть именно в неё, происходит Page Split. SQL Server создает новую страницу, переносит туда часть данных и записывает новую строку. Это вызывает интенсивный ввод-вывод. Ожидание PAGEIOLATCH_EX как раз возникает в момент, когда поток ждет подгрузки нужной страницы с диска в память для совершения вставки. Даже на быстрых дисковых массивах большое количество разрывов страниц может драматически снижать производительность INSERT.

Мониторинг дисковой очереди и кэша

Если мы видим PAGEIOLATCH_EX, необходимо проверить эффективность использования оперативной памяти. Рассмотрим счетчик Buffer Cache Hit Ratio. В идеальной системе он должен быть выше 98%. Если значение падает ниже, значит, SQL Server вынужден постоянно вытеснять данные из памяти и читать их с диска, что и порождает задержки при записи.

Также проанализируем длину очереди к диску через "Монитор ресурсов" Windows. Очередь выше 1.0 на постоянной основе свидетельствует о том, что дисковая подсистема не справляется с потоком запросов. Важно понимать, что INSERT не завершится, пока SQL Server не запишет транзакцию в лог (.ldf). Поэтому стоит убедиться, что файл лога транзакций и файл данных физически разнесены на разные дисковые массивы.

Оптимизация журнала транзакций и VLF

Скорость выполнения модифицирующих запросов напрямую зависит от состояния Virtual Log Files (VLF). Если файл журнала транзакций рос мелкими порциями, в нем образуются тысячи VLF. При каждой транзакции SQL Server вынужден управлять этой структурой, что замедляет INSERT.

Рекомендуем выполнить следующие действия для оптимизации лога:

  1. Сделать бэкап лога транзакций (если модель восстановления Full).
  2. Выполнить операцию SHRINKFILE для файла лога.
  3. Увеличить размер лога вручную одним большим блоком (например, до 4-8 ГБ) и установить разумный шаг прироста (например, 512 МБ или 1 ГБ).

Использование флагов трассировки и IFI

Для систем 1С часто оказывается полезным включение Instant File Initialization (IFI). Это позволяет SQL Server мгновенно выделять место под файлы данных без заполнения его нулями. Чтобы активировать эту возможность, пользователю, под которым запущена служба SQL Server, необходимо предоставить право Perform volume maintenance tasks в локальных политиках безопасности Windows.

Также проанализируем необходимость использования флагов трассировки:

Подписки на события и бизнес-логика

Не забудем заглянуть в конфигуратор 1С. Несмотря на то, что профайлер показывает время именно SQL-запроса, общее время Договор.Записать() может складываться из множества факторов. В "старой КА" (Комплексной автоматизации) часто встречаются тяжелые Подписки на события. Хотя они и выполняются как отдельные запросы, они могут удерживать блокировки на уровне транзакции, создавая очереди, которые косвенно замедляют даже простые операции вставки в другие таблицы.

Подведем итог. Если вы столкнулись с долгим INSERT, действуйте по алгоритму: установите MAXDOP = 1, проверьте статистику ожиданий на предмет PAGEIOLATCH_EX, убедитесь в высокой эффективности буферного кэша и при необходимости оптимизируйте журнал транзакций. Совокупность этих мер в большинстве случаев возвращает производительность системы к целевым показателям.

← На главную