При внедрении и активной эксплуатации крупных информационных систем на базе 1С:Предприятие часто возникает неприятная ситуация: документы, выполняющие запись в подчиненные периодические регистры сведений, начинают проводить транзакции крайне медленно. Анализ через технологический журнал или MS SQL Profiler показывает, что основное время тратится на операцию вставки или обновления временных таблиц и физических таблиц итогов, а СУБД выполняет операцию Table Scan (полное сканирование таблицы) там, где ожидался быстрый поиск по индексу. Проанализируем ситуацию, выясним причину такого поведения и рассмотрим подробнее все возможные варианты решения этой проблемы.
Когда мы в конфигураторе 1С взводим у периодического регистра сведений свойство «Разрешить итоги: Срез последних» (или «Срез первых»), платформа 1С автоматически создает в базе данных SQL дополнительную физическую таблицу. Для таблицы среза последних она носит имя _InfoRgSL<n>, а для среза первых — _InfoRgSF<n> (где <n> — порядковый номер таблицы в СУБД).
В этой таблице хранятся актуальные значения ресурсов в разрезе уникального набора измерений на текущий момент времени. При записи набора записей в основной регистр платформа в рамках той же транзакции выполняет обновление данных в этой сопутствующей таблице. Однако на версиях платформы 1С:Предприятие 8.3.12 и ниже существовала серьезная архитектурная недоработка: СУБД хранила таблицу _InfoRgSL как «кучу» (Heap) — структуру данных без кластерного индекса.
Это приводило к следующим последствиям:
_InfoRg<n>, но полностью игнорировался для таблицы срезов.Table Scan), так как некластерный индекс не содержал всех необходимых системных полей (таких как _Period, _RecorderTRef, _RecorderRRef).Разберем по шагам, как можно исправить эту проблему в зависимости от архитектурных ограничений вашего проекта.
Начиная с релиза платформы 8.3.13.1644, разработчики фирмы «1С» устранили эту ошибку. Теперь для физических таблиц срезов первых и последних автоматически выстраивается полноценный кластерный индекс по измерениям.
Однако простого обновления платформы недостаточно, чтобы исправить структуру базы данных на SQL-сервере. Нам необходимо принудительно запустить реструктуризацию таблиц. Выполним это одним из следующих способов:
Булево), обновить конфигурацию базы данных (нажав клавишу F7), а затем удалить этот реквизит и снова обновить конфигурацию. Платформа принудительно пересоздаст таблицы регистра в СУБД, добавив долгожданные кластерные индексы.Если проект по каким-то причинам жестко привязан к старым версиям платформы (например, 8.3.12) и обновиться в ближайшее время невозможно, самым эффективным решением будет отказаться от хранения физических таблиц среза.
Для этого выполните следующие действия:
Что изменится? Физическая таблица _InfoRgSL будет полностью удалена из базы данных SQL. Запись в регистр станет выполняться мгновенно, так как платформе больше не нужно параллельно обновлять "кучу" и тратить ресурсы на её сканирование.
При этом виртуальная таблица СрезПоследних по-прежнему останется доступной в запросах 1С! Однако теперь платформа будет получать эти данные динамически — путем построения подзапроса к основной таблице регистра _InfoRg<n> с использованием конструкции MAX(Период) и группировки по измерениям. На больших объемах данных такое динамическое получение среза через кластерный индекс основной таблицы работает в десятки раз быстрее, чем обращение к неиндексированной таблице срезов.
Платформа 1С обращается к физической таблице _InfoRgSL только тогда, когда разработчик запрашивает срез последних на текущую дату (передавая пустую дату или пустую Граница времени в параметры виртуальной таблицы). Если же в параметрах указать конкретную дату, платформа понимает, что использовать кэшированную физическую таблицу текущих итогов нельзя, и строит оптимальный запрос к основной таблице.
Если вам критически важно сохранить включенную галочку итогов, но нужно оптимизировать чтение в коде, передавайте в параметры виртуальной таблицы дату, отличную от текущей хотя бы на одну секунду назад:
// Вместо этого (запрос обратится к медленной таблице итогов):
ГраницаПериода = Новый Граница(ТекущаяДата(), ГраницаТипа.Включая);
// Используем смещение на 1 секунду (запрос обратится к индексам основной таблицы):
ГраницаПериода = Новый Граница(ТекущаяДата() - 1, ГраницаТипа.Включая);
Посмотрим на пример решения проблемы напрямую через MS SQL Server Management Studio. Этот метод часто используется системными администраторами и экспертами по технологическим вопросам для быстрого устранения "затыка" в работе СУБД без изменения конфигурации 1С.
Важное предупреждение: Данное действие формально нарушает лицензионное соглашение фирмы «1С». Кроме того, при любой последующей реструктуризации базы данных (при обновлении конфигурации или тестировании) платформа удалит созданный вами индекс, и его придется создавать заново.
Если вы осознаете риски и решили применить этот способ, выполните следующий T-SQL скрипт в вашей базе данных (заменив имя таблицы _InfoRgSL8427 на имя таблицы вашего регистра):
-- Создаем кластерный индекс по измерениям регистра для ускорения поиска и обновления данных
CREATE CLUSTERED INDEX CX_InfoRgSL8427_Dimensions ON dbo._InfoRgSL8427
(
_Fld281 ASC,
_Fld8417RRef ASC,
_Fld8418RRef ASC,
_Fld8419RRef ASC
) WITH (PAD_INDEX = OFF, STATISTICS_NORECOMPUTE = OFF, SORT_IN_TEMPDB = OFF, DROP_EXISTING = OFF, ONLINE = ON, ALLOW_ROW_LOCKS = ON, ALLOW_PAGE_LOCKS = ON) ON [PRIMARY];
После создания этого индекса операция записи в регистр перестанет вызывать блокировки и Table Scan, так как СУБД получит упорядоченную структуру данных и сможет быстро находить строки для обновления.
Если вы хотите гарантировать предсказуемое поведение СУБД независимо от версии платформы и настроек итогов, в критически важных местах конфигурации можно полностью отказаться от обращения к виртуальной таблице СрезПоследних. Вместо этого напишем классический запрос получения последних записей.
Рассмотрим пример такого запроса:
ВЫБРАТЬ
РегистрМаксимумы.Измерение1 КАК Измерение1,
МАКСИМУМ(РегистрМаксимумы.Период) КАК Период
ПОМЕСТИТЬ ВТ_МаксимальныеДаты
ИЗ
РегистрСведений.ИмяРегистра КАК РегистрМаксимумы
ГДЕ
РегистрМаксимумы.Измерение1 В (&СписокИзмерений)
СГРУППИРОВАТЬ ПО
РегистрМаксимумы.Измерение1
;
////////////////////////////////////////////////////////////////////////////////
ВЫБРАТЬ
РегистрДанные.Период КАК Период,
РегистрДанные.Измерение1 КАК Измерение1,
РегистрДанные.Ресурс1 КАК Ресурс1
ИЗ
ВТ_МаксимальныеДаты КАК МаксДаты
ВНУТРЕННЕЕ СОЕДИНЕНИЕ РегистрСведений.ИмяРегистра КАК РегистрДанные
ПО МаксДаты.Измерение1 = РегистрДанные.Измерение1
И МаксДаты.Период = РегистрДанные.Период
Этот код гарантированно задействует штатные кластерные индексы основной таблицы регистра сведений _InfoRg<n>, обеспечивая стабильную и высокую скорость выполнения операции как на чтение, так и на запись.