Почему документ открывается 20 минут: Проблема с динамическим списком и большим объемом данных в 1С?

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

Здравствуйте! Давайте вместе разберем одну из самых неприятных проблем в разработке 1С — это критически медленное открытие документов, особенно когда речь идет о работе с динамическими списками, оперирующими огромными объемами данных. Представим себе ситуацию, когда простой документ открывается целых 20 минут, что делает работу с ним практически невозможной.

Наш читатель столкнулся именно с такой проблемой: среднее время открытия документа составляет 20 минут. Данные табличной части документа хранятся в регистре сведений, который содержит порядка 4 миллионов записей на один документ, а общий объем регистра достигает 777 миллионов записей. Для отображения данных на форме используется ДинамическийСписок, создаваемый программно.

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

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

Динамические списки (ДС) в 1С — это мощный инструмент для работы с большими объемами данных. Их основная идея заключается в том, что данные считываются не сразу все, а небольшими порциями, по мере необходимости (например, при прокрутке списка). Это называется динамическим считыванием данных, и по умолчанию эта функция включена. Обычно данные считываются небольшими порциями, например, по 45 или 1000 записей, в зависимости от настроек и версии платформы.

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

  1. Отсутствие подходящих индексов: Если условия отбора и сортировки, которые мы применяем к динамическому списку, не подкреплены эффективными индексами в базе данных, СУБД может быть вынуждена выполнять полное сканирование всей таблицы. В этом случае динамическое считывание становится малоэффективным, так как для формирования даже первой порции данных требуется полный перебор.
  2. Сложный запрос: Даже если динамический список использует свойство ОсновнаяТаблица = ТаблицаРегистра, платформа формирует запрос к СУБД. Если в регистре много измерений, ресурсов, или используются виртуальные таблицы (например, срез последних), запрос может стать очень сложным. СУБД, в свою очередь, может выбрать неоптимальный план выполнения, который игнорирует преимущества динамического считывания.
  3. Отсутствие ключевых полей в произвольном запросе: Если мы используем произвольный запрос для динамического списка и не указываем ОсновнаяТаблица или КлючевыеПоля, платформа может не иметь достаточно информации для эффективного постраничного считывания, что также может приводить к загрузке больших объемов данных.

Ключевые диагностические признаки проблемы

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

1. Чрезвычайно медленный запрос ВЫБРАТЬ ПЕРВЫЕ 1 КОЛИЧЕСТВО(*)

Автор сообщил, что даже запрос, предназначенный для получения количества записей в регистре, выполняется очень долго: ВЫБРАТЬ ПЕРВЫЕ 1 КОЛИЧЕСТВО(*) ИЗ РегистрСведений Такое длительное выполнение (сравнимое со временем открытия документа) даже такого простого запроса, как ВЫБРАТЬ ПЕРВЫЕ 1 КОЛИЧЕСТВО(*) на регистре с 777 миллионами записей, является критическим индикатором серьезных проблем на уровне СУБД. Это может указывать на:

  1. Полное отсутствие подходящего индекса: СУБД вынуждена выполнять полное сканирование всей таблицы, чтобы посчитать количество записей.
  2. Сильная фрагментация таблицы или индексов: Даже при наличии индекса, его сильная фрагментация может замедлять операции.
  3. Проблемы с дисковой подсистемой: Нагрузка на ввод/вывод данных может быть слишком высокой, что замедляет любые операции с диском.

2. Порядок измерений регистра сведений

Автор также уточнил, что "измерение регистра документ является последним по метаданным". Этот факт имеет прямое отношение к производительности:

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

Для оптимальной работы рекомендуется размещать часто используемые для отборов и сортировок измерения в начале списка измерений регистра. Это позволит СУБД быстрее находить нужные данные.

Копаем глубже: Пошаговая инструкция по оптимизации

Теперь, когда мы проанализировали потенциальные причины и диагностические признаки, давайте разберем конкретные шаги, которые помогут нам решить эту проблему.

1. Профилирование запросов: Наш главный инструмент

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

  1. SQL Server Profiler (для MS SQL Server): Запустим профайлер и настроим его для отслеживания запросов к нашей базе данных 1С. Нас будут интересовать события, связанные с выполнением запросов (например, SQL:BatchCompleted, RPC:Completed) и, что особенно важно, планы выполнения запросов.
  2. Другие СУБД: Аналогичные инструменты существуют и для других СУБД (например, pg_stat_statements и EXPLAIN ANALYZE для PostgreSQL, Oracle SQL Developer для Oracle).

Во время профилирования откроем проблемный документ. Анализируя логи профайлера, мы сможем увидеть реальный SQL-запрос, который отправляется в базу данных, и, главное, понять, почему СУБД тратит на него столько времени. Возможно, это будет полное сканирование таблицы (Table Scan) вместо использования индекса (Index Seek), что является явным признаком проблемы.

2. Оптимизация запроса динамического списка

Даже если динамический список создается программно и использует ОсновнаяТаблица = ТаблицаРегистра, мы можем влиять на его поведение. Если мы создаем динамический список программно, у нас есть возможности для его детальной настройки:

  1. Упрощение запроса: Стараемся максимально упростить запрос. Избегаем ненужных полей, сложных условий ИЛИ, НЕ, ПОДСТРОКА, которые часто делают индексы бесполезными или вынуждают СУБД выполнять полное сканирование.
  2. Использование произвольного запроса с осторожностью: Если стандартный подход с ОсновнаяТаблица не дает нужного результата, можем попробовать использовать произвольный запрос. Однако здесь есть свои нюансы:
    • Обязательное указание КлючевыеПоля или ОсновнаяТаблица: При использовании произвольного запроса, критически важно явно указать ОсновнаяТаблица или задать КлючевыеПоля. Без них платформа не сможет эффективно считывать данные порциями, что приведет к загрузке всего объема. Например, если мы фильтруем по Документу, наш запрос может выглядеть так:
      
      ВЫБРАТЬ
          РегистрСведений.Документ КАК Документ,
          РегистрСведений.Измерение1,
          РегистрСведений.Ресурс1
      ИЗ
          РегистрСведений КАК РегистрСведений
      ГДЕ
          РегистрСведений.Документ = &ДокументСсылка
      
      В этом случае платформа может использовать Документ в качестве основного ключа для постраничной навигации, если он проиндексирован. Мы должны убедиться, что параметр &ДокументСсылка корректно передается в динамический список.
    • Оптимизация самого произвольного запроса: Пишем запрос максимально эффективно, используя подсказки СУБД (если уместно), напрямую учитывая имеющиеся индексы.

3. Индексы базы данных: Наша основа производительности

Как мы выяснили из медленного запроса КОЛИЧЕСТВО(*) и порядка измерений, индексы играют здесь ключевую роль. Мы должны убедиться, что они настроены оптимально:

  1. Создание эффективных индексов:
    • Индексы должны быть созданы на тех полях, по которым чаще всего устанавливаются отборы (например, по измерению Документ в нашем случае) и происходит сортировка.
    • Если регистр используется для различных типов отборов, возможно, потребуется несколько некластерных индексов.
    • Композитные индексы: Если отбор часто происходит по комбинации полей (например, Документ и Номенклатура), создадим композитный индекс (Документ, Номенклатура). Важно: порядок полей в композитном индексе имеет значение! Самые селективные поля (с наибольшим разнообразием значений) или поля, по которым чаще всего начинается отбор, должны идти первыми.
  2. Пересмотр порядка измерений регистра: Если измерение Документ действительно является последним и часто используется для отбора, мы должны рассмотреть возможность его перемещения в начало списка измерений регистра. Это позволит платформе 1С автоматически создавать более эффективные индексы. Важно: изменение порядка измерений — это серьезная операция, требующая полного тестирования и, возможно, конвертации данных. Проводить ее нужно крайне осторожно и только после полного резервного копирования.
  3. Обслуживание индексов: Регулярно перестраиваем или реорганизуем индексы, чтобы снизить фрагментацию и поддерживать их эффективность. Эту задачу обычно выполняют регламентные задания СУБД. Высокая фрагментация приводит к неэффективному чтению данных с диска.
  4. Избегайте избыточного количества индексов: Хотя индексы ускоряют чтение, они замедляют запись (добавление, изменение, удаление данных), так как каждый индекс должен быть обновлен. Находим баланс между производительностью чтения и записи.

4. Управление объемом данных: Архитектурные решения

4 миллиона записей на документ и 777 миллионов записей в регистре — это колоссальный объем данных. Даже самая лучшая индексация и оптимизация запросов могут оказаться недостаточными, если архитектура не рассчитана на такие нагрузки. Здесь мы должны задать себе несколько вопросов и рассмотреть фундаментальные изменения:

  1. Декомпозиция данных: Действительно ли все 4 миллиона записей табличной части документа необходимы для одновременного отображения? Возможно, эти данные можно разделить на несколько связанных регистров или документов? Или показывать только агрегированные данные, а детальные — по запросу? Например, если документ хранит миллионы позиций прайс-листа, возможно, сам документ должен ссылаться на отдельный справочник или регистр прайс-листов, а не хранить их напрямую.
  2. Агрегация данных: Можем ли мы хранить часть данных в агрегированном виде? Например, если нам нужна сумма по номенклатуре за период, возможно, стоит создать вспомогательный регистр, который будет хранить уже рассчитанные суммы, а не каждый раз пересчитывать их из миллионов детальных записей. Это снижает нагрузку на основную таблицу.
  3. Ограничение количества отображаемых записей:
    • В принципе, нужно стремиться к тому, чтобы в динамический список для отображения в документе попадало как можно меньше данных — только актуальные и действительно нужные.
    • Можем использовать фильтры по умолчанию, чтобы при открытии документа отображалась только часть данных (например, только "активные" позиции, записи за последний месяц и т.д.), а остальные данные подгружались по требованию пользователя через дополнительные фильтры.
  4. Партиционирование таблицы: Если СУБД поддерживает, рассмотрите возможность партиционирования таблицы регистра по, например, году или периоду. Это может значительно улучшить производительность запросов, которые фильтруются по этим полям, так как СУБД будет работать только с соответствующей партицией, а не со всей огромной таблицей.

5. Использование события ПриПолученииДанныхНаСервере (для платформы 8.3.10 и выше)

Если нам необходимо выполнить сложные вычисления или форматирование данных, которые зависят от значений из динамического списка, мы можем использовать событие ПриПолученииДанныхНаСервере для динамического списка (доступно начиная с платформы 8.3.10). Это позволяет нам:

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

6. Временные таблицы в динамических списках

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

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

← На главную