Как ускорить работу запроса Срез последних в 1С при использовании PostgreSQL

Программист 1С v8.3 (Обычные формы) Управленческий учет
← На главную

При работе крупных информационных систем на базе 1С:Предприятие в связке с СУБД PostgreSQL программисты часто сталкиваются с критическим падением производительности запросов к виртуальным таблицам, особенно к СрезПоследних. Ситуация, когда получение 15 тысяч записей из регистра цен занимает несколько минут, является классической проблемой оптимизатора PostgreSQL. В этой статье мы подробно разберем, почему это происходит, и рассмотрим проверенные методы решения задачи.

Выясним причину: почему виртуальные таблицы тормозят в PostgreSQL

Основная проблема заключается в механизме, который технические специалисты называют Predicate Pushdown (пробрасывание условий). Рассмотрим ситуацию: когда мы обращаемся к виртуальной таблице РегистрСведений.ЦеныНоменклатуры.СрезПоследних, платформа 1С генерирует сложный вложенный SQL-запрос. Этот запрос должен сначала найти максимальный период для каждой комбинации измерений, а затем соединить эти данные с основной таблицей регистра для получения ресурсов (например, цены).

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

Проанализируем основные способы решения этой проблемы, от изменения кода до системных настроек.

Решение 1: Использование временных таблиц (самый надежный метод)

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

Рассмотрим алгоритм по шагам:

  1. Выбираем измерения регистра и максимальный период во временную таблицу, сразу применяя все необходимые фильтры (по типу цен, номенклатуре и т.д.).
  2. Индексируем временную таблицу по измерениям и периоду.
  3. Соединяем временную таблицу с физической таблицей регистра для получения значений ресурсов.

Посмотрим на пример кода такого запроса:


// Шаг 1: Получаем последние периоды во временную таблицу
ВЫБРАТЬ
    ЦеныНоменклатуры.Номенклатура КАК Номенклатура,
    ЦеныНоменклатуры.ХарактеристикаНоменклатуры КАК ХарактеристикаНоменклатуры,
    МАКСИМУМ(ЦеныНоменклатуры.Период) КАК Период
ПОМЕСТИТЬ втПоследниеПериоды
ИЗ
    РегистрСведений.ЦеныНоменклатуры КАК ЦеныНоменклатуры
ГДЕ
    ЦеныНоменклатуры.ТипЦен = &ТипЦен
СГРУППИРОВАТЬ ПО
    ЦеныНоменклатуры.Номенклатура,
    ЦеныНоменклатуры.ХарактеристикаНоменклатуры;

// Шаг 2: Индексируем (опционально, зависит от объема данных)
// ИНДЕКСИРОВАТЬ ПО Номенклатура, ХарактеристикаНоменклатуры, Период;

// Шаг 3: Соединяем с основной таблицей для получения ресурсов (цены)
ВЫБРАТЬ
    втПоследниеПериоды.Номенклатура,
    втПоследниеПериоды.ХарактеристикаНоменклатуры,
    втПоследниеПериоды.Период,
    ЦеныНоменклатуры.Цена,
    ЦеныНоменклатуры.Валюта
ИЗ
    втПоследниеПериоды КАК втПоследниеПериоды
    ВНУТРЕННЕЕ СОЕДИНЕНИЕ РегистрСведений.ЦеныНоменклатуры КАК ЦеныНоменклатуры
    ПО ЦеныНоменклатуры.Номенклатура = втПоследниеПериоды.Номенклатура
        И ЦеныНоменклатуры.ХарактеристикаНоменклатуры = втПоследниеПериоды.ХарактеристикаНоменклатуры
        И ЦеныНоменклатуры.Период = втПоследниеПериоды.Период
ГДЕ
    ЦеныНоменклатуры.ТипЦен = &ТипЦен;

Важный момент: Обратите внимание, что условие ТипЦен = &ТипЦен продублировано во втором запросе. Это помогает планировщику PostgreSQL еще точнее определить индекс для физической таблицы.

Решение 2: Включение режима использования итогов среза последних

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

Выполним следующие действия:

  1. Откроем конфигуратор.
  2. Найдем нужный РегистрСведений.
  3. На закладке Данные установим флаг Разрешить итоги среза последних.
  4. Обновим конфигурацию базы данных.

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

Решение 3: Оптимизация на уровне PostgreSQL

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

Рассмотрим параметр random_page_cost. По умолчанию в PostgreSQL он равен 4.0. Для современных систем на SSD-дисках это значение слишком велико, что заставляет оптимизатор думать, будто сканирование всей таблицы (Seq Scan) выгоднее, чем использование индексов. Рекомендуется установить:

random_page_cost = 1.1

effective_cache_size = (объем оперативной памяти, доступный СУБД)

Также для 1С критически важны параметры join_collapse_limit и from_collapse_limit. Поскольку 1С генерирует запросы с множеством вложенных соединений, стандартного значения 8 часто не хватает. Попробуйте увеличить их до 20, чтобы PostgreSQL дольше пытался найти оптимальный план объединения таблиц.

Решение 4: Исправление порядка полей в индексах

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

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


DO $$ DECLARE txt text; 
BEGIN
  FOR txt IN SELECT 'DROP INDEX ' || relname || '; ' || pg_get_indexdef(relname::regclass)||';' as ddl 
        FROM pg_catalog.pg_class WHERE relkind = 'i' and relname like '_inforg%_bydims' 
  LOOP EXECUTE txt; 
  END LOOP; 
END; 
$$

Этот скрипт пересоздает индексы, что обновляет статистику и заставляет PostgreSQL заново построить планы выполнения для «проблемных» запросов.

Подведем итоги

Если ваш запрос к срезу последних на PostgreSQL работает медленно, рекомендуем придерживаться следующей последовательности действий:

  1. Проверьте параметры СУБД: убедитесь, что random_page_cost адекватен вашему дисковому массиву.
  2. Используйте временные таблицы: перепишите сложный запрос, выделив получение МАКСИМУМ(Период) в отдельный шаг с предварительной фильтрацией. Это дает 100% результат в большинстве случаев.
  3. Включите итоги: если срез всегда берется на текущую дату, использование физической таблицы итогов снимет нагрузку с процессора.
  4. Обновите статистику: убедитесь, что регламентное задание ANALYZE выполняется регулярно, чтобы оптимизатор видел реальное распределение данных в регистре.

Помните, что PostgreSQL требует более внимательного отношения к написанию запросов, чем MS SQL, но при правильном подходе (использование LATERAL в новых версиях или временных таблиц) показывает сопоставимую производительность.

← На главную