При работе крупных информационных систем на базе 1С:Предприятие в связке с СУБД PostgreSQL программисты часто сталкиваются с критическим падением производительности запросов к виртуальным таблицам, особенно к СрезПоследних. Ситуация, когда получение 15 тысяч записей из регистра цен занимает несколько минут, является классической проблемой оптимизатора PostgreSQL. В этой статье мы подробно разберем, почему это происходит, и рассмотрим проверенные методы решения задачи.
Основная проблема заключается в механизме, который технические специалисты называют Predicate Pushdown (пробрасывание условий). Рассмотрим ситуацию: когда мы обращаемся к виртуальной таблице РегистрСведений.ЦеныНоменклатуры.СрезПоследних, платформа 1С генерирует сложный вложенный SQL-запрос. Этот запрос должен сначала найти максимальный период для каждой комбинации измерений, а затем соединить эти данные с основной таблицей регистра для получения ресурсов (например, цены).
В идеальном мире СУБД должна сначала применить фильтр (например, по ТипЦен), а уже потом вычислять срез последних. Однако оптимизатор PostgreSQL (особенно в версиях до 12-й или при определенных настройках) часто не может «протолкнуть» условие фильтра внутрь подзапроса. В результате СУБД сначала вычисляет срез последних для всех записей регистра в памяти, а уже потом отсеивает нужные 15 тысяч строк. Если в регистре миллионы записей, запрос выполняется крайне долго.
Проанализируем основные способы решения этой проблемы, от изменения кода до системных настроек.
Разберем наиболее эффективный способ, который гарантирует высокую скорость работы на любой версии PostgreSQL и платформы. Вместо использования виртуальной таблицы СрезПоследних, мы реализуем алгоритм получения данных вручную через временные таблицы. Это позволит нам принудительно наложить фильтры на самом первом этапе.
Рассмотрим алгоритм по шагам:
Посмотрим на пример кода такого запроса:
// Шаг 1: Получаем последние периоды во временную таблицу
ВЫБРАТЬ
ЦеныНоменклатуры.Номенклатура КАК Номенклатура,
ЦеныНоменклатуры.ХарактеристикаНоменклатуры КАК ХарактеристикаНоменклатуры,
МАКСИМУМ(ЦеныНоменклатуры.Период) КАК Период
ПОМЕСТИТЬ втПоследниеПериоды
ИЗ
РегистрСведений.ЦеныНоменклатуры КАК ЦеныНоменклатуры
ГДЕ
ЦеныНоменклатуры.ТипЦен = &ТипЦен
СГРУППИРОВАТЬ ПО
ЦеныНоменклатуры.Номенклатура,
ЦеныНоменклатуры.ХарактеристикаНоменклатуры;
// Шаг 2: Индексируем (опционально, зависит от объема данных)
// ИНДЕКСИРОВАТЬ ПО Номенклатура, ХарактеристикаНоменклатуры, Период;
// Шаг 3: Соединяем с основной таблицей для получения ресурсов (цены)
ВЫБРАТЬ
втПоследниеПериоды.Номенклатура,
втПоследниеПериоды.ХарактеристикаНоменклатуры,
втПоследниеПериоды.Период,
ЦеныНоменклатуры.Цена,
ЦеныНоменклатуры.Валюта
ИЗ
втПоследниеПериоды КАК втПоследниеПериоды
ВНУТРЕННЕЕ СОЕДИНЕНИЕ РегистрСведений.ЦеныНоменклатуры КАК ЦеныНоменклатуры
ПО ЦеныНоменклатуры.Номенклатура = втПоследниеПериоды.Номенклатура
И ЦеныНоменклатуры.ХарактеристикаНоменклатуры = втПоследниеПериоды.ХарактеристикаНоменклатуры
И ЦеныНоменклатуры.Период = втПоследниеПериоды.Период
ГДЕ
ЦеныНоменклатуры.ТипЦен = &ТипЦен;
Важный момент: Обратите внимание, что условие ТипЦен = &ТипЦен продублировано во втором запросе. Это помогает планировщику PostgreSQL еще точнее определить индекс для физической таблицы.
В современных версиях платформы 1С (8.3.8 и выше) появилась возможность включить использование физической таблицы итогов для среза последних. Если вы используете срез на текущую дату (без передачи параметра периода), этот метод будет самым быстрым.
Выполним следующие действия:
РегистрСведений.После этого 1С создаст в базе данных реальную таблицу, в которой будут храниться только актуальные записи на текущий момент. Запрос к СрезПоследних() превратится из тяжелого вычисления в простую выборку из этой таблицы. Важно помнить: это работает только для среза на «сейчас». Если вы запрашиваете данные на прошлую дату, СУБД все равно будет выполнять вычисления.
Если программные методы не помогают или их внедрение требует слишком больших правок кода, стоит проанализировать настройки самой СУБД. Проблема часто усугубляется неверными коэффициентами стоимости операций.
Рассмотрим параметр 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 дольше пытался найти оптимальный план объединения таблиц.
Иногда стандартные индексы 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 работает медленно, рекомендуем придерживаться следующей последовательности действий:
random_page_cost адекватен вашему дисковому массиву.МАКСИМУМ(Период) в отдельный шаг с предварительной фильтрацией. Это дает 100% результат в большинстве случаев.ANALYZE выполняется регулярно, чтобы оптимизатор видел реальное распределение данных в регистре.Помните, что PostgreSQL требует более внимательного отношения к написанию запросов, чем MS SQL, но при правильном подходе (использование LATERAL в новых версиях или временных таблиц) показывает сопоставимую производительность.