Ничего не понятно, но очень интересно! По общему впечатлению - отличный материал для 1С:Эксплуататоров. Авторы статьи - герои. Которых мы не заслужили, но в которых нуждались. :)
Взял из open source - верни в open source. Золотое правило.
Не поленитесь еще теперь по своим доработкам прислать пулл реквест в репу https://github.com/postgres/postgres/pulls чтобы поддержать комьюнити. Наверняка его сразу не примут и потребуется немного постараться, но оно того стоит!
Спасибо за очередную статью, подтверждающую что не "PG плохой, MSSQL хороший", а всего лишь надо заниматься оптимизацией запросов (как и в любом другом фреймворке).
А не проще ли создавать индексы на временных таблицах. Или даже изначально использовать постоянные нежурналируемые таблицы с нужными индексами, вместо временных?
(4) Можно по-разному решить проблему, но так или иначе придется лезть в типовое решение и в дальнейшем поддерживать и обслуживать все свои доработки. А лезть в закрытие месяца не очень то и хочется)
(8) Вот более развернутый пример, для понимания того, как можно не трогая типовое решение индексировать временные таблицы:
Код на SQL
DR OP EVENT TRIGGER IF EXISTS build_index_on_tmp_tmp_tr;
DR OP FUNCTION IF EXISTS build_index_on_tmp_tmp;
CREATE OR REPLACE FUNCTION build_index_on_tmp_tmp()
RETURNS event_trigger LANGUAGE plpgsql AS $$
BEGIN
IF EXISTS (
SEL ECT 1
FR OM pg_event_trigger_ddl_commands() E
WHERE E.object_identity='pg_temp.tmp_tmp' AND tg_tag='CRE ATE TABLE' ) THEN
CRE ATE INDEX IF NOT EXISTS tmp_tmp_idx ON tmp_tmp(Id);
END IF;
END;
$$;
CRE ATE EVENT TRIGGER build_index_on_tmp_tmp_tr ON ddl_command_end
EXECUTE FUNCTION build_index_on_tmp_tmp();
DR OP TABLE IF EXISTS tmp_tmp;
CREATE TEMP TABLE tmp_tmp (Id int, some_text text NULL);
Показать
P.S. Не знаю, почему форум пробелы вставляет куда ни попадя. Победить не удалось (
Если правильно понял, свое расширение для постгрес было написано для полусения отборов по планам. А если включить техрурнал с отбором по базе и тегом plansql?
Loop join ожидает, что будет 1 строка, а получает 39 тысяч строк, отчего надолго «уходит в себя», так как на временной таблице нет индекса и приходится идти в скан (Seq Scan), а там около 200 тысяч строк.
Тут скорее речь идет о статистике и о том что postgres через несколько соединений плохо справляется с предсказанием, поэтому считая что будет мало строк выбирает Nested Loop.
(Для пересчета статистики по временным таблицам есть плагин online_analyze (online_analyze.table_type = 'temporary' включать только так), но не уверен что поможет.)
Удобнее было бы реализовать что-то вроде AQO или взять его, т.е. исправлять похожие "кривые" планы. Я бы не рекомендовал использовать глобальное отключение использования nested loop.
(12) online_analyze.table_type = 'temporary' так и настроено и PG прекрасно видит количество строк на временных таблицах, но после нескольких отборов оптимизатор говорит что будет 1 строка, и как следствие выбирает loop для соединения, который, к сожалению, получает 39 тысяч строк.
(12) Тут проблема у PostgreSQL. Статистику для временной таблицы построит или обновит VACUUM, но когда это он сделает - неизвестно. Может через секунду, а может и через час. Когда доберется до нее и как загрузка сервера позволит. Поэтому и рекомендуется после заполнения временной таблицы явно из кода запускать на нее ANALYZE.
С другой стороны, и на MS SQL я встречал изрядные тормоза, когда забывали проиндексировать временную таблицу. Поэтому индексировать временную таблицу стоит всегда, за исключением тех случаев, когда все равно по ней нужно только полное сканирование. Например, когда она затем будет использована только в FR OM без WH ERE и все остальные таблицы в JOIN имеют подходящие индексы.
С рекомендацией не использовать глобальное отключение NESTED LOOP - полностью согласен.
А вот решение, которое я применял, считаю предпочтительным. А именно, как уже писал выше, индексировать временные таблицы при их создании средствами событийного триггера, не трогая конфигурацию 1С.
Модуль online_analyze предоставляет набор функций, которые немедленно обновляют статистику после операций INSERT, UPDATE, DELETE или SELECT INTO в целевых таблицах.
т.е. отвечая на ваш вопрос - сделает сразу (если включите, конечно), модуль встроен ребятами из Postgres Pro.
А я про этот костыль и не упоминал. У него есть целый ряд других проблем, из-за чего online_analyze и не принят в mainstreem. Навскидку:
1. Каждый DML запрос, обновляющий таблицу, ожидает завершение обновления ее статистик.
2. У автора (Сигаева) даже в планах нет использовать background worker.
3. При откате транзакции статистика не откатывается, что в последствии может привести к непредсказуемому поведению планировщика запросов.
(20)
Ну так про явный вызов ANALYZE я и писал выше. Но радикальное решение проблемы ТС, повторюсь, в индексации временной таблицы при ее создание событийным триггером. И эффект от этого будет существенно выше, чем от статистик по неиндексированной таблице, заставляющих, по сути, СУБД строить хеш индекс таблицы в памяти при каждом запросе, обращающемся к ней.
И это еще если JOIN по равенству полей. А если нет? HASH JOIN тогда и не поможет. А правильный индекс - еще как поможет.
(21) 1С рандомно генерирует имя временной таблицы при выполнении запроса, имена полей тоже не исключение, хоть и зависят от порядка в тексте запроса 1С. Про составные поля и говорить не стоит. Такое отлавливать триггером в PG так себе занятие, проще уже индекс средствами 1С накинуть раз уж на то пошло.
(22)
Если через ПОМЕСТИТЬ, то по счетчику. Но нам или важна структура таблицы, или только одно поле, с именем заканчивающимся на RRef. А уж проверить структуру таблицы в триггере, если уж очень хочется - не проблема. Мне до структуры доходить редко приходилось. Чаще, триггером индексирую первое поле, имя которого заканчивается на RRef, чего уже хватает.
Про проще, не знаю. В случае ПОМЕСТИТЬ, это уж точно код править. Лично мне проще на plpgsql один раз функцию написать, чем после каждого обновления патчить типовую конфигурацию.
(25) А я и точно и не определял. Если создается временная таблица с нужным мне количеством полей и первым полем, заканчивающимся на RRef - я по нему и индексировал. С одной стороны, на индексации каких-то лишних таблиц я терял секунды. Но с другой стороны, при закрытии выигрывал часы.
(26) Если то самое первое RRef никак не участвует ни в отборах ни в соединениях, и по факту надо индексировать какое-нибудь 3 или 4 по счету RRef, то такую ситуацию как то дополнительно обрабатывали? или просто не было нужды?
Количество полей временной таблицы тоже вещь не постоянная, легко может поменяться запрос в новом релизе, соответственно придется править триггер. Ну или поле было не составным, а стало составным (или на оборот), а с виду запрос 1С и не менялся, то тоже надо править триггер. Получается что все равно надо следить и контролировать.
(27)
Редко, но бывают ситуации, когда приходится анализировать не только количество, но и типы, длины и порядок всех полей таблицы. Благо метаданные таблицы доступны.
Саму информацию о временных таблицах и их метаданных собираю такой функцией на событийном триггере:
CREATE OR REPLACE FUNCTION catch_tmp_tables_info()
RETURNS event_trigger LANGUAGE plpgsql AS $func$
DECLARE
obj record;
metadata text;
BEGIN
FOR obj IN
SEL ECT *
FR OM pg_event_trigger_ddl_commands()
WHERE object_type='table' AND schema_name='pg_temp'
LOOP
SELECT STRING_AGG(attname||$$,$$||attnum::varchar||$$,$$||atttypid::varchar
||$$($$||attlen::varchar||$$,$$||atttypmod::varchar||$$) $$||attnotnull::varchar,$$;$$)
FR OM pg_attribute
WH ERE attrelid=obj.objid AND attnum>0
INTO metadata;
RAISE NOTICE '% ddl commands: % "%"', tg_tag, obj.object_identity, metadata;
END LOOP;
END;
$func$;
Показать
А то, что после нового релиза может как появится новая проблема, так и решиться имеющаяся - это уже жизнь. От этого мы никуда не денемся.
Даже по имени базы.
...
А если это общий сервер для тестирования и там много баз?
Имхо, тут постгрес идеологически по-другому подходит. С позиции 1 инстанс = 1 база. Обратное, хоть теоретически и возможно, но убивает полностью процесс бекапов/восстановления в двоичном виде.
Коллеги, я же правильно понял, что вы использовали Postgres из дистрибутивов 1С? А PostgresPro Std/Ent не пробовали?
Было бы полезно знать есть ли такие болячки в pgpro.