При администрировании и разработке крупных, высоконагруженных систем на платформе 1С:Предприятие под управлением СУБД MS SQL Server технические специалисты часто сталкиваются со снижением производительности. Одной из наиболее сложных проблем являются ожидания типа PAGELATCH_EX (монопольная защелка страницы буфера) и PAGELATCH_SH (общая защелка страницы буфера). Ситуация нередко обостряется после проведения оптимизации: программист переписывает тяжелые запросы, перенося данные во временные таблицы для построения оптимальных планов СУБД, но система внезапно начинает зависать еще сильнее.
В этой статье мы подробно проанализируем природу этих ожиданий, выясним причины их возникновения в экосистеме 1С и разберем по шагам практические методы их устранения на уровне СУБД и кода 1С.
Для начала давайте разберемся, в чем заключается фундаментальная разница между логическими блокировками (Locks) и физическими защелками (Latches) в MS SQL Server. Это критически важно для диагностики, так как методы борьбы с ними абсолютно различны:
LCK_M_X, LCK_M_S). Логическими блокировками мы управляем из кода 1С с помощью механизма управляемых блокировок.PAGELATCH_SH. Если поток собирается изменить данные на странице, он требует монопольного доступа через PAGELATCH_EX. Защелки работают на микросекундном уровне и не имеют прямого отношения к логике транзакций 1С.Если в системе фиксируются длительные ожидания PAGELATCH_EX, это означает, что множество параллельных потоков одновременно пытаются модифицировать одну и ту же физическую страницу памяти размером 8 КБ. Происходит жесткая конкуренция за физический ресурс, из-за чего процессоры тратят время на ожидания и смену контекста (что проявляется ростом системных ожиданий SOS_SCHEDULER_YIELD).
Рассмотрим стандартный механизм записи в регистры накопления или сведений 1С. Кластерный индекс основной таблицы движений регистра обычно строится по полям Период, Регистратор, НомерСтроки. Поскольку проведение документов происходит в реальном времени, новые записи всегда имеют текущую дату и время.
В физической структуре B-дерева СУБД новые записи с монотонно возрастающим значением периода укладываются строго последовательно в самую последнюю страницу индекса. Если в систему одновременно пишут десятки потоков (например, при интенсивной выписке чеков или проведении документов реализации), все эти потоки пытаются захватить монопольный доступ (PAGELATCH_EX) к одной и той же физической странице. Это явление называется «конкуренцией за последнюю страницу при вставке» (last-page insert contention).
Проанализируем классическую ошибку оптимизации, с которой столкнулся автор темы на форуме. Пытаясь ускорить работу тяжелых запросов, программист перенес промежуточные выборки во временные таблицы. Это правильный шаг для стабилизации планов запросов СУБД, однако под высокой параллельной нагрузкой это спровоцировало лавинообразный рост ожиданий на системных страницах базы данных TempDB.
Дело в том, что при создании, индексировании и удалении временных таблиц в TempDB СУБД непрерывно обращается к специальным страницам распределения пространства базы данных:
Когда десятки сеансов 1С каждую секунду создают и уничтожают временные таблицы, они одновременно пытаются обновить одни и те же системные страницы распределения в TempDB. Ситуация становится катастрофической, если файлы базы данных TempDB настроены некорректно.
Если вы столкнулись с ростом PAGELATCH_EX после активного внедрения временных таблиц в коде, первым делом обратите внимание на конфигурацию TempDB. Никогда не оставляйте базу данных TempDB с одним файлом данных на одном диске! Это гарантированный способ парализовать систему при параллельной работе.
Выполним следующие настройки административного характера:
TempDB должно соответствовать числу логических ядер процессора, доступных операционной системе, но не более 8 файлов на первом этапе. Если ядер больше 8, создайте ровно 8 файлов данных. Если мониторинг по-прежнему показывает ожидания на страницах распределения (номера страниц в базе 2 имеют вид 2:1:1, 2:1:3 и т.д.), увеличивайте количество файлов порциями по 4 штуки.TempDB должны иметь строго одинаковый начальный размер и одинаковый шаг авторасширения (например, по 512 МБ или 1024 МБ). Это заставит MS SQL Server распределять нагрузку по файлам абсолютно равномерно по алгоритму Proportional Fill, снижая конкуренцию за защелки распределения страниц.TempDB на сверхбыстрых накопителях NVMe SSD или корпоративных дисковых массивах с минимальным временем отклика.TempDB (таблицы вроде sys.sysschobjs) в оптимизированные под оперативную память таблицы In-Memory OLTP. Это полностью устраняет защелки на страницах метаданных системных каталогов. Включим эту опцию с помощью скрипта:
ALTER DATABASE CURRENT SET MEMORY_OPTIMIZED_TEMPDB_METADATA = ON;
После этого потребуется перезапустить службу SQL Server. Обратите внимание, что эта функция требовательна к объему оперативной памяти.
Использование автоматического режима блокировок в 1С (уровень изоляции SERIALIZABLE на стороне СУБД) — это главный источник конфликтов между читающими и пишущими транзакциями. При автоматических блокировках любой тяжелый отчет или расчет себестоимости, читающий данные из регистров, накладывает разделяемые блокировки (S-locks), которые блокируют операции записи (INSERT). Это приводит не только к транзакционным ожиданиям типа LCK_M_..., но и провоцирует лавину защелок PAGELATCH_SH на страницах данных при попытках параллельного чтения.
Проанализируем стратегию перехода на управляемый режим:
ALTER DATABASE [ИмяВашейБазы1С] SET SINGLE_USER WITH ROLLBACK IMMEDIATE;
ALTER DATABASE [ИмяВашейБазы1С] SET READ_COMMITTED_SNAPSHOT ON;
ALTER DATABASE [ИмяВашейБазы1С] SET ALLOW_SNAPSHOT_ISOLATION ON;
ALTER DATABASE [ИмяВашейБазы1С] SET MULTI_USER;
Этот шаг кардинально разгрузит рабочие таблицы от конфликтов чтения/записи, перенеся нагрузку по чтению версий в правильно настроенную СУБД TempDB.
Если защелки PAGELATCH_EX продолжают возникать на основных таблицах регистров из-за интенсивной параллельной вставки, применим инженерные подходы на уровне СУБД:
ALTER INDEX [ИмяИндекса] ON [ИмяТаблицыРегистра] REBUILD WITH (OPTIMIZE_FOR_SEQUENTIAL_KEY = ON);
Помните, что при реструктуризации базы данных платформой 1С индексы могут быть пересозданы с параметрами по умолчанию. Рекомендуем настроить регламентное задание на стороне SQL Server, которое будет периодически проверять и включать данную опцию для критически нагруженных таблиц.
ALTER INDEX [ИмяИндекса] ON [ИмяТаблицыРегистра] REBUILD WITH (FILLFACTOR = 85);
Посмотрим на проблему с точки зрения разработчика 1С. Постоянное создание и уничтожение временных таблиц в коротких, часто вызываемых транзакциях (например, при оформлении чеков ККМ) — это неоптимальный паттерн проектирования. Рассмотрим, как правильно работать с временными таблицами в коде.
Если запрос выполняется многократно в секунду, постараемся избавиться от временных таблиц, оптимизировав исходный запрос с помощью правильных соединений и индексированных виртуальных таблиц с жесткими параметрами-отборами. Например, вместо следующей конструкции:
// Неоптимальный частый вызов временной таблицы
Запрос = Новый Запрос;
Запрос.Текст =
"ВЫБРАТЬ Ссылка ПОМЕСТИТЬ ВтНоменклатура ИЗ Справочник.Номенклатура ГДЕ ЭтоГруппа = ЛОЖЬ;
|
|ВЫБРАТЬ * ИЗ РегистрНакопления.ТоварыНаСкладах.Остатки(,, Номенклатура В (ВЫБРАТЬ Ссылка ИЗ ВтНоменклатура))";
Лучше передать массив ссылок напрямую в параметры виртуальной таблицы, избегая лишних транзакций по созданию таблиц в TempDB:
// Оптимальное решение без создания временных таблиц
Запрос = Новый Запрос;
Запрос.Текст = "ВЫБРАТЬ * ИЗ РегистрНакопления.ТоварыНаСкладах.Остатки(&Период, Номенклатура В (&МассивНоменклатуры))";
Запрос.УстановитьПараметр("МассивНоменклатуры", СписокСсылок);
Если же временная таблица действительно необходима из-за сложности алгоритма, обязательно создавайте индекс по связующим полям. Это ускорит чтение и снизит время удержания защелок на страницах данных:
Запрос = Новый Запрос;
Запрос.Текст =
"ВЫБРАТЬ Ссылка ПОМЕСТИТЬ ВтТовары ИЗ Справочник.Номенклатура ИНДЕКСИРОВАТЬ Ссылка ГДЕ Склад = &Склад;
|
|ВЫБРАТЬ * ИЗ РегистрНакопления.ТоварыНаСкладах.Остатки(,, Номенклатура В (ВЫБРАТЬ Ссылка ИЗ ВтТовары))";
Также обратим внимание на уничтожение временных таблиц. При использовании объекта МенеджерВременныхТаблиц не обязательно вручную вызывать метод Закрыть() внутри коротких процедур, так как это генерирует явную команду DROP TABLE для каждой таблицы, нагружая системный каталог TempDB. Позволим платформе очистить их автоматически по завершении работы объекта менеджера временных таблиц.
Борьба с ожиданиями PAGELATCH_EX и PAGELATCH_SH требует комплексного подхода, объединяющего усилия системного администратора СУБД и разработчика 1С. Разделение базы данных TempDB на файлы по количеству ядер процессора, переход конфигурации на управляемые блокировки с активацией версионности строк (RCSI) на СУБД и грамотное проектирование запросов без избыточного использования временных таблиц в высокочастотных транзакциях гарантированно стабилизируют систему и устранят узкие места производительности.