Решение проблем производительности UDF - ручное кэширование - PullRequest
0 голосов
/ 03 февраля 2009

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

У меня есть довольно много случаев, когда нужно вызывать UDF, скажем, 5 миллионов строк (и я в значительной степени думал, что не было никакого способа обойти это).

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

Рассмотрим UDF, который принимает набор входных данных и возвращает результат, основанный на сложной логике, но для набора входных данных в 5-метровых строках, скажем, есть только 100 000 различных входных данных, и поэтому он будет производить только 100 000 различных наборов результатов (Мои конкретные случаи варьируются от процентных ставок до сложных назначений кода, но все они дискретны - фундаментальный момент с этой техникой заключается в том, что вы можете просто определить, сработает ли уловка, запустив SELECT DISTINCT).

Я обнаружил, что делая что-то вроде этого:

INSERT INTO PreCalcs
SELECT param1
       ,param2
       ,dbo.udf_result(param1, param2) AS result
FROM (
    SELECT DISTINCT param1, param2 FROM big_table
)

Когда PreCalcs соответствующим образом проиндексирован, комбинация этого с:

SELECT big_table.param1
    ,big_table.param2
    ,PreCalcs.result
FROM big_table
INNER JOIN PreCalcs
    ON PreCalcs.param1 = big_table.param1
    AND PreCalcs.param2 = big_table.param2

Вы получаете ОГРОМНОЕ повышение производительности. Очевидно, то, что что-то является детерминированным, не означает, что SQL Server кэширует прошлые вызовы и повторно использует их, как можно подумать.

Единственное, на что вам нужно обратить внимание, это когда NULL разрешены, тогда вам нужно тщательно исправить свои объединения:

SELECT big_table.param1
    ,big_table.param2
    ,PreCalcs.result
FROM big_table
INNER JOIN PreCalcs
    ON (
        PreCalcs.param1 = big_table.param1
        OR COALESCE(PreCalcs.param1, big_table.param1) IS NULL
    )
    AND (
        PreCalcs.param2 = big_table.param2
        OR COALESCE(PreCalcs.param2, big_table.param2) IS NULL
    )

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

Полагаю, вопрос в том, почему ручное кэширование так необходимо - разве это не значит, что сервер знает, что функция является детерминированной? И если это имеет такое большое значение, и если UDF такие дорогие, почему оптимизатор просто не делает это в плане выполнения?

Ответы [ 2 ]

3 голосов
/ 03 февраля 2009

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

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

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

1 голос
/ 03 февраля 2009

Как SQL Server узнает, что у вас есть 100 000 дискретных комбинаций в пределах 5 миллионов строк?

Используя таблицу PreCalcs, вы просто запускаете udf более чем на 100 тыс. Строк, а не на 5 млн. Строк, прежде чем снова расширяться.

Ни один из существующих оптимизаторов не смог бы разгадать эту полезную информацию. Скаляр udf - это черный ящик.

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

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

...