Почему запись строкового представления составного типа документа в ТаблицуЗначений работает медленно в 1С?

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

Представим, что мы столкнулись с проблемой, когда казалось бы простая операция записи данных из выборки запроса в ТаблицуЗначений начинает занимать непропорционально много времени, превращаясь в значительное "узкое место" производительности. Эта ситуация хорошо знакома многим разработчикам 1С, и мы подробно разберем, почему она возникает и как ее эффективно решить.

Исходная проблема: замедление при заполнении ТаблицыЗначений

Давайте рассмотрим конкретный сценарий, когда при заполнении ТаблицыЗначений строковыми данными, полученными из запроса, производительность резко падает. Автор темы описал классическую ситуацию:

Создается чистая ТаблицаЗначений:


КвалификаторыСтроки = Новый КвалификаторыСтроки(100);
ОписаниеСтроки = Новый ОписаниеТипов("Строка", ,КвалификаторыСтроки);
ТЗРасшифровка = Новый ТаблицаЗначений;
ТЗРасшифровка.Колонки.Добавить("Перемещение",ОписаниеСтроки);
ТЗРасшифровка.Колонки.Добавить("Склад");
ТЗРасшифровка.Колонки.Добавить("СкладПолучатель");
ТЗРасшифровка.Колонки.Добавить("Количество");
ТЗРасшифровка.Колонки.Добавить("Сумма");

Затем эта таблица заполняется данными из выборки запроса:


ВыборкаДеталки = ВыборкаПодразделения.Выбрать();
Пока ВыборкаДеталки.Следующий() Цикл 
    НовСтрока = ТЗРасшифровка.Добавить();
    НовСтрока.Перемещение = ВыборкаДеталки.Перемещение;
    ЗаполнитьЗначенияСвойств(НовСтрока,ВыборкаДеталки,,"Перемещение"); 
КонецЦикла;

В этом коде ВыборкаДеталки.Перемещение является строковым представлением документа, полученным в запросе. Несмотря на кажущуюся простоту операции, при большом объеме данных (например, 200 тысяч строк, как в примере автора) запись данных в ТаблицуЗначений занимает недопустимо много времени, а объем потребляемой памяти может достигать 1 гигабайта.

Выясняем причины замедления: функция "Представление()" и составные типы

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

  1. Особенности функции Представление() в запросах.

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

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

    Если объект имеет сложный модуль менеджера с процедурой ОбработкаПолученияПредставления, которая содержит длительные операции (например, обращение к другим данным, сложную логику форматирования), то каждый такой неявный вызов Представление() будет выполняться крайне медленно.

    Для составных типов данных, когда поле может содержать ссылки на несколько разных типов объектов (например, на различные документы или справочники), формирование представления становится ещё более ресурсоемким. Платформа должна определить конкретный тип значения из составного, а затем получить его представление, что может повлечь за собой проверку всех возможных типов и, соответственно, выполнить многочисленные дополнительные обращения к базе данных.

  2. Составные типы и "разыменование через точку".

    Проблема усугубляется, когда мы работаем с составными типами и получаем значения "через точку" (например, ВыборкаДеталки.Перемещение, если Перемещение — это ссылочное поле составного типа). Это называется разыменованием ссылочного поля.

    При таком разыменовании система неявно создает дополнительные запросы и соединяет полученные таблицы. Представьте ситуацию, когда наше поле Перемещение может быть ссылкой на 50 различных видов документов. Если мы просто обратимся к ВыборкаДеталки.Перемещение, платформа 1С может попытаться неявно соединиться со *всеми* таблицами этих 50 видов документов, чтобы получить необходимую информацию (например, для формирования представления или для доступа к другим полям ссылки). Это колоссальная нагрузка на СУБД и значительное замедление выполнения запроса.

    Мы должны стремиться избегать такого неявного разыменования и заранее определять необходимые данные в запросе.

  3. Влияние значений NULL в запросах.

    В ситуации, описанной автором, поле Перемещение могло быть результатом ПОЛНОГО СОЕДИНЕНИЯ и, следовательно, содержать значения NULL. Это важный момент, так как NULL — это не просто пустая строка или Неопределено; это особое типообразующее значение, указывающее на отсутствие значения. Любые операции с NULL обычно возвращают NULL.

    При попытке получить строковое представление от NULL или выполнить неявное преобразование NULL в строку, платформа также выполняет определённые действия, которые, хотя и могут дать пустую строку, в контексте составных типов и неоптимизированных запросов, могут способствовать общему замедлению. Мы должны явно обрабатывать NULL в запросах, чтобы контролировать результат и избежать непредсказуемого поведения.

  4. Роль КвалификаторовСтроки.

    Автор темы пробовал изменять КвалификаторыСтроки (например, КвалификаторыСтроки(100)). Эти квалификаторы определяют максимальную длину строкового поля в таблице значений. Хотя их неправильное использование может привести к ошибкам (например, обрезке строки) или излишнему потреблению памяти при хранении, напрямую на скорость *формирования представления* они влияют незначительно. Проблема, как мы выяснили, лежит глубже – в источнике данных и способе его получения. Если же мы подставляем строку, которая по длине превышает квалификаторы, может происходить неявная обрезка, которая сама по себе не является причиной замедления, но указывает на несоответствие данных и может маскировать истинную проблему.

Решение проблемы: Оптимизация получения данных непосредственно в запросе

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

Рассмотрим пример, как мы могли бы оптимизировать получение поля "Перемещение", предполагая, что оно может быть ссылкой на документ "Перемещение товаров" или "Поступление товаров и услуг", а также может быть NULL:


ВЫБРАТЬ
    Т1.СсылкаНаДокумент КАК ПеремещениеДок,
    ЕСТЬNULL(ВЫБОР
        КОГДА Т1.СсылкаНаДокумент ССЫЛКА Документ.ПеремещениеТоваров
            ТОГДА ВЫРАЗИТЬ(Т1.СсылкаНаДокумент КАК Документ.ПеремещениеТоваров).Номер + " от " + ФОРМАТ(ВЫРАЗИТЬ(Т1.СсылкаНаДокумент КАК Документ.ПеремещениеТоваров).Дата, "ДФ=dd.MM.yyyy")
        КОГДА Т1.СсылкаНаДокумент ССЫЛКА Документ.ПоступлениеТоваровУслуг
            ТОГДА ВЫРАЗИТЬ(Т1.СсылкаНаДокумент КАК Документ.ПоступлениеТоваровУслуг).Номер + " от " + ФОРМАТ(ВЫРАЗИТЬ(Т1.СсылкаНаДокумент КАК Документ.ПоступлениеТоваровУслуг).Дата, "ДФ=dd.MM.yyyy")
        ИНАЧЕ "Неизвестный тип документа"
    КОНЕЦ, "Документ не указан") КАК ПеремещениеПредставление,
    Т1.Склад КАК Склад,
    Т1.СкладПолучатель КАК СкладПолучатель,
    Т1.Количество КАК Количество,
    Т1.Сумма КАК Сумма
ИЗ
    ВашаИсходнаяТаблицаДанных КАК Т1 // Например, из временной таблицы или регистра

В этом запросе мы явно:

  1. Используем ВЫРАЗИТЬ(<Выражение> КАК <Тип>), чтобы явно указать платформе, с каким типом мы работаем в данный момент, избегая неявного разыменования через точку для составного типа.
  2. Формируем строковое представление, объединяя Номер и Дату документа, что является гораздо более быстрой операцией, чем вызов Представление().
  3. Используем ЕСТЬNULL() для обработки случаев, когда ссылка на документ может быть неопределена (например, при внешних соединениях), обеспечивая корректное и предсказуемое строковое значение.

После того как мы получили строковое представление непосредственно в запросе (как поле ПеремещениеПредставление в нашем примере), заполнение таблицы значений становится простым и быстрым, поскольку мы передаем уже готовую строку:


// Предполагаем, что ВыборкаПодразделения содержит поля из оптимизированного запроса
ВыборкаДеталки = ВыборкаПодразделения.Выбрать();
Пока ВыборкаДеталки.Следующий() Цикл 
    НовСтрока = ТЗРасшифровка.Добавить();
    НовСтрока.Перемещение = ВыборкаДеталки.ПеремещениеПредставление; // Теперь это уже готовая строка!
    НовСтрока.Склад = ВыборкаДеталки.Склад;
    НовСтрока.СкладПолучатель = ВыборкаДеталки.СкладПолучатель;
    НовСтрока.Количество = ВыборкаДеталки.Количество;
    НовСтрока.Сумма = ВыборкаДеталки.Сумма;
    // Если полей много и их имена совпадают, можно использовать ЗаполнитьЗначенияСвойств,
    // но только после того, как все поля-источники преобразованы в запросе.
    // Например: ЗаполнитьЗначенияСвойств(НовСтрока, ВыборкаДеталки);
КонецЦикла;

Этот подход позволяет нам избежать дорогостоящих операций по формированию представления на стороне клиента или неявных дополнительных запросов платформы. Мы передаем в таблицу значений уже готовую строку, что значительно повышает производительность и устраняет причину "глюка".

Дополнительные рекомендации по работе с ТаблицамиЗначений и оптимизации запросов

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

  1. Избегайте использования Представление() или неявного получения представлений для ссылочных полей в запросах.

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

    При работе с составными типами, если вы точно знаете, какой тип объекта ожидается, используйте ВЫРАЗИТЬ(<Выражение> КАК <Тип>) для явного приведения типа перед доступом к полям или формированием представления. Это предотвращает попытки платформы "догадаться" о типе и выполнять избыточные операции.

  2. Осторожнее с "разыменованием через точку" для полей составного типа.

    Как мы уже обсудили, если поле может быть ссылкой на множество разных объектов, получение других полей через точку (например, Т1.Перемещение.СкладОтправитель, если Перемещение — составной тип) приведет к неявному соединению со всеми возможными таблицами, что крайне неэффективно и может мгновенно "уронить" производительность запроса.

    Если реквизит составного типа используется в соединениях, отборах или сортировке, рекомендуется оставлять в его составе только ссылочные типы, избегая нессылочных (Строка, Число, Дата и т.п.). В противном случае производительность запросов может заметно снижаться из-за особенностей физического хранения составных типов в СУБД.

  3. Всегда явно обрабатывайте NULL.

    Для корректной работы с данными, особенно при использовании внешних или полных соединений, всегда используйте функцию ЕСТЬNULL(Значение, РезультатЕслиNULL). Это позволяет нам явно контролировать результат в случае отсутствия значения и избежать непредвиденного поведения, а также ошибок при дальнейшей обработке данных. Использование ВЫБОР КОГДА Значение ЕСТЬ NULL ТОГДА ... ИНАЧЕ ... КОНЕЦ также возможно, но ЕСТЬNULL чаще бывает более лаконичной и понятной.

  4. Оптимизируйте работу с ТаблицаЗначений при больших объемах данных.

    ТаблицаЗначений — удобный, но не всегда самый производительный инструмент для очень больших объемов (более 50 000 строк). Если не требуются преимущества индексирования ТаблицыЗначений или другие специфические возможности, рассмотрите альтернативы, например, прямое использование результатов запроса или временных таблиц. При работе с ТаблицейЗначений, минимизируйте количество колонок и их длину, если это возможно.

    Для ускорения заполнения, помимо оптимизации запроса, можно использовать метод ЗаполнитьЗначенияСвойств(), передавая в него выборку целиком, если имена полей совпадают. Однако, как мы видим из примера автора, если поле-источник нуждается в преобразовании, то прямое использование ЗаполнитьЗначенияСвойств() только на части полей может быть неоптимальным. Лучше заранее подготовить все поля в запросе.

  5. Проводите диагностику производительности.

    Используйте Технологический журнал и встроенные инструменты замера производительности 1С (как это сделал автор) для выявления "узких мест" в запросах и коде. Обращайте внимание на длительные запросы, соединения с подзапросами и виртуальными таблицами, несоответствие индексов и условий, а также неоптимальные условия с использованием ИЛИ. Регулярный анализ этих данных поможет своевременно выявлять и устранять проблемы.

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

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

← На главную