Создаем свои индексы для баз 1С. Со своей структурой и настройками!

Поговорим о неплатформенных индексах для информационных баз 1С. Об особенностях их использования, целесообразности и подводных камнях.

О чем речь

Это всего лишь еще одна статья об избитой теме неплатформыенных индексов в информационных базах платформы 1С. Мы поговорим о “плохих” практиках тюнинга, которые с одной стороны запрещены лицензионным соглашением фирмы 1С, а с другой являются наиболее эффективным средством оптимизации производительности запросов. Эдакий запретный плод!

Не рекомендуется к прочтению, если к Вам относится хотя бы один из следующих пунктов:

  • Используется файловый режим работы информационной базы
  • Нет никаких проблем производительности и стабильности информационной системы
  • Считаете большой ошибкой выход за пределы экосистемы платформы 1С
  • Вы сотрудник фирмы “1С”

Все, что Вы прочитаете далее, должно остаться внутри Вашей головы и никогда не переходить в практическую плоскость. Еще раз повторяю: все что сказано далее — это плохие практики, нерекомендуемые фирмой “1С” и нарушающие ее лицензионное соглашение, противоречащие материалам подготовки к экзамену “1С:Эксперт по технологическим вопросам”, снижающие карму и просто имеющие подводные камни планетарного масштаба.

Будете читать дальше? Тогда отлично, поехали!

Зачем все это

Платформа 1С создает множество индексов самостоятельно по настройкам объектов метаданных конфигурации. Также разработчики сами могут в ограниченном режиме влиять на создание и изменение индексов. Самую полную информацию о платформенных индексах смотрите здесь.

Посмотрели? Задаетесь вопросом почему нужны еще какие-то дополнительные индексы?

Ответ на этот вопрос простой. Он даже проще, чем можно себе представить!

Индексы нужны для повышения эффективности запросов на чтение при поиске данных. При этом вариантов выборки данных, фильтров, группировок и порядка сортировки может быть огромное количество. Чем больше полей в таблице, тем больше этих вариантов. Даже чисто гипотетически трудно себе представить ситуацию, когда платформенные индексы могли бы полностью удовлетворить большую часть запросов. Частично — может быть, полностью — никогда.

Ситуация усугубляется еще и тем, что добавление индексов через свойство полей “Индексировать” в конфигураторе имеет примитивные возможности для их настройки. Фактически, Вы можете только выбрать три варианта (да и то не всегда):

  • Не индексировать (с этим и так все понятно)
  • Индексировать (индекс по выбранному полю + доп. поля для уникальности комбинации значений)
  • Индексировать с дополнительным упорядочиванием (индекс по выбранному полю + поля упорядочивания, по которым обычно сортируют данные этой таблицы)

Повторюсь, что примеры того, как влияют эти настройки на индексы можно посмотреть здесь. Почему же этих вариантов недостаточно?

Вот Вам пару вопросов для обдумывания:

  • А что если нужно создать индекс сразу по нескольким реквизитам?
  • А если нужен индекс по пометке удаления?
  • Как построить эффективный индекс по любому полю с типом “Булево”?
  • Как добавить индексы в служебные таблицы, недоступные непосредственно в метаданных конфигурации (некоторые основные таблицы, таблицы итогов и др.?
  • Можно ли сделать покрывающий индекс, чтобы исключить операции Lookup к основной таблице или кластерному индексу?

Таких вопросов можно задавать много. Вы говорите, что все это можно решить средствами платформы? Или что это все не нужно и разработчики конфигурации просто ошиблись в архитектуре. Тогда прошу напишите в комментариях Ваши решения, но у меня будет лишь пару вопросов:

  1. Почему бы не использовать произвольные индексы на уровне базы данных с произвольной структурой и настройками?
  2. Для чего тогда были придуманы фильтрованные индексы, покрывающие индексы, произвольные составные индексы, если платформа 1С их не использует? От лукавого? 🙂

А можно примеры?

Конечно, для наглядности рассмотрим в качестве примеров ответы на те самые вопросы, которые я написал выше. И так, погнали!

А что если нужно создать индекс сразу по нескольким реквизитам

Предположим, что у нас есть справочник “ФизическиеЛица” следующей структуры (некоторые поля пропущены). В самой таблице примерно 2 млн. записей.

Поле 1С Имя SQL Тип SQL
Ссылка _IDRRef binary(16)
ПометкаУдаления _Marked binary(1)
Код _Code nvarchar(10)
Наименование _Description nvarchar(50)
ДатаРождения _Fld11066 datetime2(0)
Фамилия _Fld105061 nvarchar(50)
Имя _Fld105062 nvarchar(50)
Отчество _Fld105063 nvarchar(50)
ДатаСоздания _Fld115447 datetime2(0)
РазделительДанных _Fld1551 numeric(7,0)

В конфигурации есть запрос поиска физического лица по комбинации полей Фамилия + Имя + Отчество + ДатаРождения.

ВЫБРАТЬ
ФизическиеЛица.Ссылка КАК Ссылка
ИЗ
Справочник.ФизическиеЛица КАК ФизическиеЛица
ГДЕ
ФизическиеЛица.Фамилия = &Фамилия
И ФизическиеЛица.Имя = &Имя
И ФизическиеЛица.Отчество = &Отчество
И ФизическиеЛица.ДатаРождения = &ДатаРождения

Чтобы ускорить поиск и исключить операции полного сканирования таблицы справочника необходимо создать индекс. Какой бы индекс Вы создали средствами платформы?

Конечно, можно поставить свойство “Индексирование” в “Индексировать” для каждого реквизита, но что это даст? СУБД сможет использовать один индекс в каждой конкретной операции плана запроса, при этом также будет учитываться актуальность статистики. Запрос 1С конвертируется в такой SQL-запрос.

SELECT
T1._IDRRef
FROM dbo._Reference477 T1
WHERE ((T1._Fld1551 = @P1)) -- Разделитель данных, есть во всех типовых конфигурациях
AND ((T1._Fld105061 = @P2) -- Фамилия
AND (T1._Fld105062 = @P3) -- Имя
AND (T1._Fld105063 = @P4) -- Отчество
AND (T1._Fld11066 = @P5)) -- Дата рождения

Взгляните на план его выполнения ниже (некоторые части разбиты на несколько строк для удобства чтения).

Nested Loops(Inner Join, OUTER REFERENCES:([T1].[_IDRRef], [T1].[_Fld1551]) OPTIMIZED)
|--Merge Join(Inner Join,
|    |            MERGE:([T1].[_Fld1551], [T1].[_IDRRef])=([T1].[_Fld1551], [T1].[_IDRRef]),
|    |            RESIDUAL:([DB].[dbo].[_Reference477].[_Fld1551] as [T1].[_Fld1551] = [DB].[dbo].[_Reference477].[_Fld1551] as [T1].[_Fld1551]
|    |                AND [DB].[dbo].[_Reference477].[_IDRRef] as [T1].[_IDRRef] = [DB].[dbo].[_Reference477].[_IDRRef] as [T1].[_IDRRef]))
|    |--Index Seek(OBJECT:([DB].[dbo].[_Reference477].[_Reference477_ByFieldFld11066] AS [T1]),
|    |                SEEK:([T1].[_Fld1551]=[@P1] AND [T1].[_Fld11066]=[@P5]) ORDERED FORWARD)
|    |--Index Seek(OBJECT:([DB].[dbo].[_Reference477].[_Reference477_ByFieldFld105062] AS [T1]),
|    |                SEEK:([T1].[_Fld1551]=[@P1] AND [T1].[_Fld105062]=[@P3]) ORDERED FORWARD)
|--Clustered Index Seek(OBJECT:([DB].[dbo].[_Reference477].[_Reference477HPK] AS [T1]),
|                         SEEK:([T1].[_Fld1551]=[DB].[dbo].[_Reference477].[_Fld1551] as [T1].[_Fld1551]
|                             AND [T1].[_IDRRef]=[DB].[dbo].[_Reference477].[_IDRRef] as [T1].[_IDRRef]),
|                        WHERE:([DB].[dbo].[_Reference477].[_Fld105061] as [T1].[_Fld105061]=[@P2] 

Описание основных действий при выполнении плана запроса следующие.

  Порядок   Операция   Количество прочитанных строк   Описание
1. Index Seek 94 Поиск по индексу “_Reference477_ByFieldFld11066” для реквизита “ДатаРождения”. Повезло, для выбранной даты рождения всего 94 значения, хорошая селективность.
2. Index Seek 10006 Поиск по индексу “_Reference477_ByFieldFld105062” для поля “Имя”. Тут все намного хуже, т.к селективность у этого поля ниже, ведь имя у многих физических лиц может совпадать.
3. Merge Join 2 Объединение результатов 1 и 2 операции методом слияния. В результате получаем две фактически одинаковые строки.
4. Clustered Index Seek 1 Выполняется операция Key Lookup для получения полей запроса, которые не были получены в предыдущих операциях. Key Lookup как известно всегда достаточно тяжелая операция.
5. Nested Loops 1 Вложенным циклом соединяются результаты 3 и 4 операции и получаем итоговый результат запроса — всего 1 строку.

Итог:

  • 419 логических чтений
  • 11 миллисекунд времени выполнения
  • 16 миллисекунд CPU
  • Выборка некоторых частей плана запроса отбирает 10000 записей, но в итоговый результат отбирается всего 1

Вот вам и индексы. 4 индекса, а эффективности никакой. Но нас ничто не остановит, мы создадим свой индекс с произвольными полями и нужной структурой!

CREATE UNIQUE NONCLUSTERED INDEX [_ByNameAndBirthday] ON [dbo].[_Reference477]
(
[_Fld105061] ASC, -- Фамилия
[_Fld105062] ASC, -- Имя
[_Fld105063] ASC, -- Отчество
[_Fld11066] ASC,  -- Дата рождения
[_IDRRef] ASC     -- Ссылка
)

Выполним запрос поиска еще раз и вот результат!

Index Seek(OBJECT:([DB].[dbo].[_Reference477].[_ByNameAndBirthday] AS [T1]),
SEEK:([T1].[_Fld105061]=[@P2] AND [T1].[_Fld105062]=[@P3]
AND [T1].[_Fld105063]=[@P4] AND [T1].[_Fld11066]=[@P5]),
WHERE:([DB].[dbo].[_Reference477].[_Fld1551] as [T1].[_Fld1551]=[@P1]) ORDERED FORWARD) 

План запроса стал значительно проще.

  Порядок   Операция   Количество прочитанных строк   Описание
1. Index Seek 1 Операция поиска в некластеризованном индексе “_ByNameAndBirthday”. Поскольку индекс содержит все необходимые поля выборки запроса, то обращение к кластерному индексу отсутствует, т.е. индекс является покрывающим.

Итог:

  • 337 логических чтений
  • 2 миллисекунд времени выполнения
  • 0 миллисекунд CPU (фактически значение незначительное, поэтому не было отловлено трассировкой)
  • План выполнения содержит только одну операцию “Index Seek”, которая фактически читает только 1 строку.

Пусть Вас не смущает, что запрос и в начальном варианте выполнялся быстро, ведь это только простой пример. Представьте рабочее окружение, тысячи или десятки тысяч запросов. Тогда разница даже на таких часто выполняемых запросах будет заметна!

Результаты говорят сами за себя. Создать подобный индекс средствами платформы 1С просто нет возможности. Но теперь Вы знаете, какой огромный потенциал для оптимизации у Вас есть.

А если нужен индекс по пометке удаления

Еще один наглядный пример — это индекс по пометке удаления. Средствами платформы добавить индекс по пометке удаления нет возможности, т.к. это стандартный реквизит и настройка “Индексирование” для него просто недоступна. Немного приблизим задачу к настоящей и скажем, что нужно отбирать помеченные на удаление элементы с учетом реквизита “ДатаСоздания”.

И так, платформа не даст создать такой индекс, но мы то знаем решение!

CREATE NONCLUSTERED INDEX [_ByDeletionMarkAndCreationDate] ON [dbo].[_Reference477]
(
[_Marked] ASC,   -- Пометка удаления
[_Fld115447] ASC,-- Дата создания
[_IDRRef] ASC    -- Ссылка
)
-- Сделаем фильтрованный индекс
-- В индекс попадают только те записи, у которых установлена пометка удаления
WHERE [_Marked] = 0x01

Что это за условие “WHERE”? SQL Server поддерживает фильтрованные индексы, в которых можно ограничить какие данные в него попадут. Если нужна более подробная информация, то Welcome! Самое главное, что нужно знать — фильтрованные индексы улучшают производительность, качество плана выполнения, расходы на обслуживание и хранение. Очень жаль, что платформа 1С не использует такие возможности СУБД.

Проверим новый индекс запросом.

ВЫБРАТЬ
ФизическиеЛица.Ссылка КАК Ссылка
ИЗ
Справочник.ФизическиеЛица КАК ФизическиеЛица
ГДЕ
ФизическиеЛица.ПометкаУдаления
И ФизическиеЛица.ДатаСоздания МЕЖДУ &НачалоПериода И &КонецПериода

И вот план запроса.

Nested Loops(Inner Join, OUTER REFERENCES:([Expr1007], [Expr1008], [Expr1009]))
|--Merge Interval
|    |--Concatenation
|         |--Compute Scalar(DEFINE:(([Expr1002],[Expr1003],[Expr1001])
|         |               =GetRangeWithMismatchedTypes([@P2],NULL,(22))))
|              |--Constant Scan
|         |--Compute Scalar(DEFINE:(([Expr1005],[Expr1006],[Expr1004])
|         |               =GetRangeWithMismatchedTypes(NULL,[@P3],(42))))
|              |--Constant Scan
|--Index Seek(OBJECT:([DB].[dbo].[_Reference477].[_ByDeletionMarkAndCreationDate] AS [T1]),
|  SEEK:([T1].[_Marked]=0x01 AND [T1].[_Fld115447] > [Expr1007] AND [T1].[_Fld115447] < [Expr1008]),
|  WHERE:([DB].[dbo].[_Reference477].[_Fld1551] as [T1].[_Fld1551]=[@P1]) ORDERED FORWARD)  

То что надо! Подробно, как в прошлый раз, описывать план запроса не будем, но вот что стоит заметить: единственная значимая операция здесь — это “Index Seek”, которая как-раз и использует наш новый индекс “_ByDeletionMarkAndCreationDate”. Никаких обращений к основной таблице / кластерному индексу не выполнялось, то есть индекс полностью удовлетворяет условиям и полям выборки запроса, является покрывающим.

Итог:

  • Время выполнения 7 миллисекунд
  • Количество логических чтений = 459
  • Время затраченное CPU 16 миллисекунд
  • План запроса простейший, самая значимая часть — это поиск по индексу “_ByDeletionMarkAndCreationDate”
  • Количество прочитанных строк — 3 (столько помеченных на удаление элементов за указанный период)

Для интереса создадим такой же индекс, но без фильтра по пометке удаления. Т.к. пример слишком простой, то разницы в результатах выполнения запроса мы не увидим, но размеры индексов будут разительно отличаться:

  • фильтрованный индекс занимает 1 страницу и включает в себя 3 строки конечного уровня.
  • полный индекс занимает 10319 страниц и содержит 2451400 строк конечного уровня.

А если не видно разницы, то зачем платить больше? 🙂

Таким же способом можно добавлять индексы для любых полей с типом “Булево” и это всегда будет эффективнее, чем добавлять индексы платформенными средствами.

Можно ли сделать покрывающий индекс

Конечно, и мы это уже сделали в двух предыдущих примерах, где созданные некластерные индексы полностью удовлетворяли условиям и выбираемым полям в запросе.

Покрывающий индекс — это такой индекс, который полностью удовлетворяет условиям запроса. В таких случаях в планах запроса будут отсутствовать операции типа Key Lookup, “добирающие” необходимые поля из таблицы или кластерного индекса (если у таблицы нет кластерного индекса, то операция называется RID Lookup).

Можно лишь добавить, что злоупотреблять покрывающими индексами не стоит и нужно хорошо подумать, прежде чем их создавать. Но это относится вообще ко всем индексам, подходите к ним с умом. Покрывающие индексы могут создаваться либо включением необходимых полей непосредственно в индексируемые поля, либо в покрывающие поля (INCLUDE). Например, можно создать индекс по ФИО + ДатеРождения, но дополнить его полями “Наименование” и “Код”. О преимуществах индекса с включенными полями можно ознакомиться здесь, но главное — это возможность значительно повысить производительность за счет включения в индекс всех необходимых для запроса полей без учета ограничения длины ключа и типов данных.

CREATE UNIQUE NONCLUSTERED INDEX [_IncludeIndex] ON [dbo].[_Reference477]
(
[_Fld105061] ASC, -- Фамилия
[_Fld105062] ASC, -- Имя
[_Fld105063] ASC, -- Отчество
[_Fld11066] ASC,  -- Дата рождения
[_IDRRef] ASC     -- Ссылка
)
INCLUDE (
-- Включенные столбцы
[_Code],  -- Код
[_Description]  -- Наименование
)

Теперь, если выполнить запрос из предыдущего примера, но с выбором полей “Наименование” и “Код”, то новый индекс позволит выполнить его максимально эффективно.

ВЫБРАТЬ
ФизическиеЛица.Ссылка КАК Ссылка,
ФизическиеЛица.Наименование,
ФизическиеЛица.Код
ИЗ
Справочник.ФизическиеЛица КАК ФизическиеЛица
ГДЕ
ФизическиеЛица.Фамилия = &Фамилия
И ФизическиеЛица.Имя = &Имя
И ФизическиеЛица.Отчество = &Отчество
И ФизическиеЛица.ДатаРождения = &ДатаРождения

А теперь представьте какие возможности бы у нас были, если бы такие индексы можно было настраивать через метаданные конфигурации.

Как добавить индексы в служебные таблицы

Рассмотрим еще один пример, когда неплатформенные индексы являются единственным эффективным решением. Оговорюсь — можно будет не создавать в этом случае индекс, а делать костыли в виде дополнительных объектов метаданных, которые обрастут различными обработчиками, проверками и т.д., но это явно не лучший путь.

Итак, у нас есть регистр бухгалтерии “Хозрасчетный”. Думаю, что все с ним знакомы. В конфигурации есть запросы к физической таблице регистра нескольких видов.

ВЫБРАТЬ
Хозрасчетный.Регистратор,
Хозрасчетный.НомерСтроки,
Хозрасчетный.СчетКт,
Хозрасчетный.Сумма
ИЗ
РегистрБухгалтерии.Хозрасчетный КАК Хозрасчетный
ГДЕ
Хозрасчетный.Активность = &Активность
И Хозрасчетный.СчетДт = &СчетДт
И Хозрасчетный.Период МЕЖДУ &НачалоПериода И &КонецПериода

А также вот такой запрос.

ВЫБРАТЬ
Хозрасчетный.Регистратор,
Хозрасчетный.НомерСтроки,
Хозрасчетный.СчетДт,
Хозрасчетный.Сумма
ИЗ
РегистрБухгалтерии.Хозрасчетный КАК Хозрасчетный
ГДЕ
Хозрасчетный.Активность = &Активность
И Хозрасчетный.СчетКт = &СчетКт
И Хозрасчетный.Период МЕЖДУ &НачалоПериода И &КонецПериода

Индексов в основной таблице регистра бухгалтерии, которые бы удовлетворяли этим запросом, просто нет. Частично запросы покрываются индексом по счету ДТ и индексом по счету КТ, но с некоторыми оговорками:

  1. Необходимо обязательно указать фильтр по организации, чтобы фильтр по периоду работал эффективно.
  2. Платформенные индексы не учитывают флаг активности проводок, т.к. судя по всему изначально задумывалось, что большинство данных будет получаться из виртуальных таблиц (но это не точно 🙂
  3. Нет ни одного покрывающего индекса, который бы полностью удовлетворял запросам в т.ч. и по выбираемым полям.

На индексы физической таблицы регистров бухгалтерии можно влиять в ограниченном режиме с помощью настроек метаданных. Но в любом случае, включать в индексы такие поля как “Активность”, “СчетДТ”, “СчетКТ” не получится. В этом случае можно создать два неплатформенных индекса.

-- Индекс для первого запроса
CREATE NONCLUSTERED INDEX [_CustomIndex1] ON [dbo].[_AccRg1595]
(
[_Fld1551] ASC, -- Разделитель данных
[_AccountDtRRef] ASC, -- Счет ДТ
[_Period] ASC, -- Период
[_Active] ASC -- Активность
)
INCLUDE (  [_RecorderTRef],
[_RecorderRRef], -- Регистратор
[_LineNo], -- Номер строки
[_AccountCtRRef], -- Счет КТ
[_Fld1599] -- Сумма
)

-- Индекс для второго запроса
CREATE NONCLUSTERED INDEX [_CustomIndex2] ON [dbo].[_AccRg1595]
(
[_Fld1551] ASC, -- Разделитель данных
[_AccountCtRRef] ASC, -- Счет ДТ
[_Period] ASC, -- Период
[_Active] ASC -- Активность
)
INCLUDE (  [_RecorderTRef],
[_RecorderRRef], -- Регистратор
[_LineNo], -- Номер строки
[_AccountDtRRef], -- Счет КТ
[_Fld1599] -- Сумма
)

Подробнее на этом примере останавливаться не будем, главное было показать, что таким подходом можно оптимизировать запросы к любым таблицам, в т.ч. и служебным, которые скрыты от разработчиков 1С: таблицы итогов, некоторые части физических таблиц и др.

Например, в одной из версий платформы были проблемы с таблицами итогов среза первых и среза последних для регистров сведений. Суть проблемы была в отсутствии кластерного индекса для этих таблиц, в результате чего запросы к ним выполнялись не самым оптимальным способом. Но с помощью “магии” неплатформенных индексов эту ситуацию можно было бы быстро исправить, а так пришлось бы ждать версии 8.3.13, в которой эта проблема была решена (см. раздел “Оптимизации”).

А в чем подвох?

Самый главных подвох, подводный камень и просто неприятность — при реструктуризации базы данных средствами платформы 1С все те индексы, что Вы создадите скриптами самостоятельно — будут удалены. Вам потребуется заново запустить эти скрипты после реструктуризации.

Тут сразу стоит оговориться, что удалены они будут только у тех таблиц, которые непосредственно и подверглись реструктуризации. Но этот факт не особо успокаивает, ведь всегда есть вероятность того, что при развертывании пакета обновления для конфигурации разработчик / администратор забудет запустить этот скрипт и на следующий день программа будет работать не как ожидалось. Ну, если это было обновление от поставщика конфигурации, то конечно всегда можно выкрутиться, что это дело именно в обновлении и в следующем релизе все поправят :).

Не стоит забывать и про еще один подводный камень — это нарушение лицензионного соглашения фирмы “1С”, а именно 65 пункта, в котором явно сказано, что использовать недокументированные возможности нельзя ни при каких обстоятельствах, даже если сильно хочется. Думайте сами — решайте сами.

На вопрос с лицензионным соглашением техническими средствами мы повлиять никак не сможем, но вот проблему сохранения и поддержки наших собственных индексов решить все таки можно. Как? Один из вариантов смотрите ниже.

Идем своим путем

В далеком 2014 году на глаза попала статья от Brent Ozar про костыльный подход создания индексов с помощью DDL-триггеров. Смысл его был в том, что при создании таблицы запускался наш произвольный скрипт, который бы и добавлял нужные индексы. Это действительно “особый” подход, только перейдите на эту страницу и посмотрите на изображение 🙂

Даже если и приходилось в то время создавать неплатформенные индексы, то заморачиваться с триггерами для их поддержки не приходилось. Достаточно было запускать нужные скрипты Job’ами в ночное время и проблема решалась.

Спустя пару лет встретился с еще одним материалом, на этот раз в центре сообщества разработчиков 1С — на ИнфостартеАлексей Бочков описывал подход по использованию сжатия таблиц и индексов средствами SQL Server, а в качестве инструмента сохранения сжатия при реструктуризации предлагал использовать тот же подход — через DDL-триггеры. Понял, что тема актуальна и используется многими. Особенно порадовал комментарий от Михаила Максимова.

В той или иной степени несколько лет использовал DDL-триггеры для разных баз с целью упростить сопровождение индексов (создание новых и изменение платформенных), файловых групп, сжатия, сегментирования, логирования изменений БД и др. задач. Но когда различных произвольных скриптов стало слишком много, то частично решил задачу сопровождения следующим образом:

  1. Создал служебную базу с настройками сжатия и произвольными правилами обработки событий создания таблиц и индексов в виде скриптов.
  2. Добавил скрипты предоставления прав для служебных баз.
  3. Создал глобальные триггеры обработки этих событий.
  4. Для того, чтобы снизить риски ошибок скриптов в произвольных правилах, все неплатформенные индексы и другие произвольные действия выполняются после включения следующей настройки:
SET XACT_ABORT OFF;

Инструкция XACT_ABORT указывает, выполняет ли SQL Server автоматический откат текущей транзакции, если инструкция языка Transact-SQL вызывает ошибку выполнения. В моем случае, если такая ошибка происходит, то реструктуризация продолжается в штатном режиме, а информация об ошибке записывается в таблицу логов для последующего разбора проблемы. Пример использования этой инструкции ниже.

CREATE TRIGGER [CustomSettingsMaintenance_OnTableCreate]
ON ALL SERVER
AFTER CREATE_TABLE
AS

BEGIN
SET NOCOUNT ON;

-- В случае возникновения ошибок продолжаем работу
SET XACT_ABORT OFF;

DECLARE @SchemaName SYSNAME,
@TableName SYSNAME,
@DatabaseName SYSNAME,
@cmd nvarchar(max)

SELECT @TableName = EVENTDATA().value('(/EVENT_INSTANCE/ObjectName)[1]','SYSNAME')
SELECT @SchemaName = EVENTDATA().value('(/EVENT_INSTANCE/SchemaName)[1]','SYSNAME')
SELECT @DatabaseName = EVENTDATA().value('(/EVENT_INSTANCE/DatabaseName)[1]','SYSNAME');

-- Здесь запускаем скрипт создания индекса
-- с учетом параметров @TableName, @SchemaName, @DatabaseName

-- Возвращаем значение по умолчанию для ситуаций с ошибками в транзакции
SET XACT_ABORT ON;

END

Таким образом, удается поддерживать произвольные настройки баз данных платформы 1С, не нарушая работу штатных механизмов. Конечно, там не так радужно, но за несколько лет использования критических ошибок не было обнаружено, все живы и здоровы. Проблемы в основном возникают, если такими способами начинаем поддерживать файловые группы или сегментирование, там нужно учитывать некоторые нюансы.

Посмотреть примеры использования DDL-триггеров для поддержки индексов и других настроек можно здесь. Если найдете проблему или будут вопросы — создавайте Issue, постараюсь ответить. По ссылке описание как создать служебную базу и триггеры, а также начать ее использование.

Вместо заключения

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

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

P.S. Повторю еще раз — ни в коем случае не делайте все то, что Вы здесь прочитали! Это плохие практики разработки, ни к чему хорошему они не приведут!

P.P.S. Но если Вы все же решились, то желаю Вам удачи!

31 Comments

  1. Repich

    Насколько я понимаю, в случае использования нового режима реструктуризации, самодельные индексы в общем случае останутся на месте.

    Reply
  2. МихаилМ

    ddl триггеры появились в ms sql 2005. а статья в 2018 . ну лучше поздно…

    Reply
  3. МихаилМ

    (1) можно таблицы подменять представлениями (например для использования общего кладра(фиаса)) .

    (0) решена ли в Ваших разработках проблема долгого копирования данных (по 1000 записей) из старой таблицы в новую ?

    Reply
  4. nomadon

    Действия направленные против лицензионной политики преследуются законом?

    Reply
  5. YPermitin

    (4) Я не могу здесь Вам дать точный ответ на этот вопрос.

    Reply
  6. YPermitin

    (1) на сколько я понимаю и «ДА» и «НЕТ».

    Но подробнее не скажу, т.к. плотно не изучал работу новой реструктуризации. Была попытка использования в начале ее появления, но столкнулся с …. непреодолимыми проблемами на тот момент.

    Reply
  7. YPermitin

    (2) Не думаю, что вопрос, почему такое никто не написал раньше, относится ко мне 🙂

    Reply
  8. YPermitin

    (3) Это не проблема неплатформенных индексов. Я бы вообще не назвал это проблемой.

    Но есть известные пути ускорения реструктуризации на больших таблицах, когда такой путь «перезаливки» данных из старой таблицы в новую мешал обновлению информационной базы. Имеет ли смысл его здесь описывать.

    Reply
  9. Repich

    (6) Мы в пятницу кстати натолкнулись на преодолимые проблемы, была проблема в конфигурации, которая для старого режима была вполне себе проходной, а вот для нового оказалась непреодолимой, в результате которой падала в exception.

    Reply
  10. geron4

    (4) Никто Вас преследовать не будет, проблема может возникнуть, если Вы с Вашей конфигурацией обратитесь в 1С с какой-нить претензией, вероятно Вам откажут в помощи.

    Reply
  11. SkyJack

    Большое спасибо за статью. Добавил в закладки.

    Reply
  12. palsergeich

    (4) 1с на обучении говорит что нельзя, но если прям ваще надо надо, то можно. Потому что иногда это единственный выход.

    Но официально они это не одобряют.

    Reply
  13. palsergeich

    (10)

    (4) 1с на обучении говорит что нельзя, но если прям ваще надо надо, то можно. Потому что иногда это единственный выход.

    Но официально они это не одобряют.

    Не совсем так, для оказания помощи они потребуют привести базу к типовому функционалу, потому что за кастомные механизмы они не отвечают

    Reply
  14. herfis

    Я бы очень тщательно взвешивал за и против, принимая решение об использовании своих индексов. Даже при наличии хороших спецов по сиквелу. ИМХО, имеет смысл только на тяжелых продуктовых базах, где без тонких настроек не обойтись и которые на постоянном сопровождении у спецов с поставленной культурой разработки. База знаний, инструкции и т.п.

    (1) Я бы на это не полагался. Случаи разные бывают, особенно учитывая что новый режим опционален.

    Reply
  15. YPermitin

    (14) Полностью поддерживаю Ваши слова.

    Без знаний SQL Server за это дело лучше вообще не браться.

    Reply
  16. herfis

    (15)

    Без знаний SQL Server за это дело лучше вообще не браться.

    Проблема в том, что «знание SQL Server» — это примерно как «знание боевых искусств». Его всю жизнь получать можно 🙂 Слишком много ньюансов и неоднозначных компромиссов, познание которых требует глубокой практики.

    Reply
  17. YPermitin

    (16) Главное не сдаваться 🙂

    Так можно про любую сферу сказать, в т.ч. и 1С.

    Reply
  18. ImHunter

    (0) Не описан еще один подвох.

    СУБД будет тратить доп.ресурсы на актуализацию еще одного (добавленного) индекса при CRUD-операциях изменении данных в проиндексированной таблице.

    Reply
  19. herfis

    (17) Ясен пень. Просто меня умиляют формулировки типа «ну, конечно нужно иметь знания SQL Sever». Насколько глубокие — вот вопрос из вопросов 🙂

    (18) Это типа само собой входит в «знание SQL Server». Как и оверхед по дисковому пространству. Который даже в случае обычных индексов может быть очень существенным. Вы ведь не будете создавать доп-индексы на маленьких табличках? 🙂 Про покрывающие индексы я вообще молчу.

    Reply
  20. YPermitin

    (19)

    Ясен пен

    Думаю, что тут надо по следующему принципу: если добавляешь свой индекс, то все последствия должен четко понимать: и дисковое пространство, и на сколько он будет востребован, будет ли эффективным или можно лучше; риски, если платформа 1С его снесет; и др.

    Никто же не спорит.

    Reply
  21. herfis

    (20)

    Никто же не спорит.

    Так и я не спорю. И статья хорошая.

    Просто ИМХО в таких статьях нужно больше запугивать расписывая возможные недостатки и жирнее намекать на необходимость получения дополнительных знаний 🙂 Желательно со ссылками на хорошие профильные материалы.

    Ведь целевая аудитория — кто? Те, кто вряд ли обладает достаточными знаниями и способен оценить степень их нехватки. Иначе зачем им эта статья?

    Reply
  22. YPermitin

    (21) Картинки к статье должно быть достаточно :)))

    Reply
  23. Slava_prog

    Спасибо очень познавательно. Хотелось бы аналогичный материал для PostgreSQL

    Reply
  24. YPermitin

    (23) Все что здесь сказано относится и к PG, но с некоторыми оговорками (большими и маленькими).

    На заметку взял, но заниматься PG не очень хочется 🙂

    Reply
  25. aximo

    (24) хорошая статья, напишите — на базах какого объема вы стали делать подобное?

    по практическому опыту — у нас доросла торговая база до 7 млн. документов за 2 года — много это или нет. Потом, было принято решение сделать срез 🙂

    Reply
  26. YPermitin

    (25) от 100 ГБ и выше, в т.ч. и базы больше 1 ТБ.

    Срез это больше как обезболивающее с побочными эффектами 🙂

    Но не осуждаю! )

    Reply
  27. Darklight

    А теперь представьте какие возможности бы у нас были, если бы такие индексы можно было настраивать через метаданные конфигурации.

    Представляю это почти каждый рабочий день 😉

    Может, когда-нибудь да сделают. Но, если сделают, это точно выйдет за рамки PROF-лицензии на платформу — это уже тонкая настройка, не для всех (надо ограничить шаловливые ручонки) — это функционал действительно для тех, только, кто знает толк в преимуществах КОРПоративных фишек платформы, и купил соответствующую лицензию.

    А вообще — я себе такую функционал представляю в рамках инструмента, за пределами конфигуратора. Размышляя о том, что нас может когда-нибудь ждать в 1С Предприятие 9 — мне видится логичным создания отдельного инструмента по мониторингу и тюненгу производительности + основные фишки, касающиеся администрирования ИБ и кластерной инфраструктуры тоже там же должны быть: этакий пылесос, совмещающий средства по настройке отдельных параметров в СУБД (ну там регламентное обслуживание хотя бы, ведение статистики и распараллеливание запросов…) + вот такие фишки по настройке индексов (включая сбор данных из СУБД по желаемым индексам), ну и заодно настройка сегментирования и файловых групп + некоторый функционал ЦУП по настройке анализу тех журнала, сбору счетчиков, здесь же и анализ журнала регистрации + ЦКК для анализа стабильности системы и возникших ошибок + средства администрирования кластера тоже надо включить в этот инструмент + управление лицензиями тоже здесь же + я бы ещё и управление пользователями правами пользователей тоже включил бы в этот инструмент + тут же должен быть контроль за внешними компонентами, внешними обработками и прочими программными модулями — что разрешено, что запрещено (поэлементно или с разрешёнными ЭЦП поставщиков — ну тут в основном имеются в виду программисты, которые это всё будут подготавливать) + инструментарий по настройке резервного копирования. И заниматься эти функционалом должны, по хорошему, не программисты, а специальные администраторы (баз данных) или эксперты по тех. вопросам. А в конфигурации, программистам дал бы возможность создавать, в метаданных как бы свои параметризованные источники данных (как виртуальные таблицы, а на языке СУБД — представления или даже процедуры, т.к. нужна поддержка временных таблиц) — тогда большая часть запросов к данным должна переместиться в такие источники, которые смог бы уже анализировать администратор баз данных в своём инструменте (собирая и анализируя статистику их использования) — и создавать нужные индексы. Да, управление агрегатами тоже бы вынес из конфигуратора в этот новый инструмент. Ну не должен программист решать такие тонкие нюансы производительности — в тех, компаниях, где это действительно актуально должен быть свой специалист по вопросам производительности СУБД и 1С, ну или хотя бы должен проводиться периодический внешний аудит.

    И всё это должно быть в одном инструменте, реализованным не как конфигурация, а как часть платформы — но в виде отельного приложения с отдельным уровнем доступа.

    Reply
  28. YPermitin

    (27)

    Представляю это почти каждый рабочий день 😉

    Боюсь, что мы этого никогда не увидим от фирмы «1С» и будем мечтать и дальше, потому что это выходит далеко за пределы назначения самой платформы. Это скорее аналог некоторой платформы, как Microsoft .NET, которая обросла огромной экосистемой.

    Для платформы 1С это нереально, т.к. задачи иные и уровень разработки другой.

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

    Reply
  29. Darklight

    (28)1С может сама и не делать — а лицензировать создание таких инструментов другим разрабочтикам — с перекладыванием ответственности на них. Но платформа 1С: Предприятие (в отличии от Microsoft .NET) является коммерческим продуктом — и компания 1С, продавая её, делает на этом бизнес. Тому пример — разделение лицензий платформы (пока в серверной части) на МИНИ/ПРОФ/КОРП с разной стоимостью — чтобы заполнить разные сегменты рынка. Чтобы более дорогие лицензии были востребованы они должны включать в себя расширенный инструментарий, особенно тот, который как раз нужен потребителям в этом сегменте — чтобы у них был стимул переходить на более дорогие решения.

    Reply
  30. YPermitin

    (29) в этом случае — да, согласен.

    Reply
  31. kuza_87

    Спасибо за статью, очень помогло. Создал для документа индекс по дате и пометке удаления. На всякий пожарный сделал перестроение, реорганизацию, очистил процедурный кэш. Делаю запрос с отбором по дате + пометке удаления. Смотрю в профайлере, а запрос всё равно происходит к индексу, где нет пометки удаления. Как заставить оптимизатор делать запрос к нужному индексу? Или он решил что ему дешевле сделать запрос к непокрывающему индексу?

    Reply

Leave a Comment

Ваш адрес email не будет опубликован. Обязательные поля помечены *