Скалярная производительность UDF в SQL Server 2005 - PullRequest
1 голос
/ 22 декабря 2008

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

В этой таблице около 10 миллионов записей, и есть индекс над полями Lat / Long

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

Для моих примеров ниже, скажем, речь идет о [40, 140], а мой радиус в градусах составляет 2 градуса.

Я пробовал это двумя способами:


1) Я создал UDF для вычисления квадрата расстояния между двумя точками, и я запускаю этот UDF в запросе.

SELECT Lat, Long FROM Table   
WHERE (Lat BETWEEN 38 AND 42)   
  AND (Long BETWEEN 138 AND 142)  
  AND dbo.SquareDistance(Lat, Long, 40, 140) < 4

Сначала я фильтрую по квадрату, чтобы ускорить запрос и позволить SQL использовать индекс, а затем уточняю его, чтобы сопоставить только записи, которые попадают в круг, с моим UDF.


2) Запустите запрос, чтобы получить квадрат (такой же, как прежде, но без последней строки), введите ВСЕ эти записи в мой код ASP.Net и вычислите круг на стороне ASP.Net (та же идея, рассчитайте квадрат расстояния, чтобы сохранить вызов Sqrt и сравнить с квадратом моего радиуса).


К моему удивлению, вычисление круга на стороне .Net примерно в 10 раз быстрее, чем с использованием UDF, что заставляет меня поверить, что я делаю что-то ужасно неправильно с этим UDF ...

Это код, который я использую:

CREATE FUNCTION [dbo].[SquareDistance] 
(@Lat1 float, @Long1 float, @Lat2 float, @Long2 float)
RETURNS float
AS
BEGIN
    -- Declare the return variable here
    DECLARE @Result float
    DECLARE @LatDiff float, @LongDiff float

    SELECT @LatDiff = @Lat1 - @Lat2
    SELECT @LongDiff = @Long1 - @Long2

    SELECT @Result = (@LatDiff * @LatDiff) + (@LongDiff * @LongDiff)

    -- Return the result of the function
    RETURN @Result

END

Я что-то здесь упускаю?
Разве использование UDF в SQL Server не должно быть намного быстрее, чем передача примерно на 25% больше записей, чем необходимо .Net, с накладными расходами DataReader, связью между процессами и чем-то еще?

Есть ли что-то, что я делаю ужасно неправильно в той UDF, которая заставляет его работать медленно?
Есть ли способ улучшить его?

Большое спасибо!

Ответы [ 4 ]

3 голосов
/ 22 декабря 2008

При использовании UDF существует много накладных расходов .

Даже кодирование в строке может быть неэффективным, поскольку индекс не может быть использован, хотя здесь предложения BETWEEN должны сокращать объем данных, которые необходимо сократить.

Чтобы расширить идею G Mastros, отделите бит выбора от квадратного бита. Это может помочь оптимизатору.

SELECT
    Lat, Long
FROM
    (
    SELECT
        Lat, Long
    FROM 
        Table   
    WHERE
        (Lat BETWEEN 38 AND 42)   
        AND
        (Long BETWEEN 138 AND 142)
    ) foo
WHERE
    ((Lat - 40) * (Lat - 40)) + ((Long - 140) * (Long - 140))  < 4

Редактировать: Вы можете уменьшить фактические расчеты. Эта следующая идея может уменьшить количество вызовов с 7 до 5

    ...
    SELECT
        Lat, Long,
        Lat - 40 AS LatDiff, Long - 140 AS LongDiff
    FROM 
    ...
    (LatDiff * LatDiff) + (LongDiff * LongDiff)  < 4
    ...

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

3 голосов
/ 22 декабря 2008

Вы можете улучшить производительность этого UDF, НЕ объявляя переменные и делая ваши вычисления более оперативными. Это, вероятно, немного улучшит производительность, но (но, вероятно, не сильно).

CREATE FUNCTION [dbo].[SquareDistance] 
(@Lat1 float, @Long1 float, @Lat2 float, @Long2 float)
RETURNS float
AS
BEGIN
    Return ( SELECT ((@Lat1 - @Lat2) * (@Lat1 - @Lat2)) + ((@Long1 - @Long2) * (@Long1 - @Long2)))
END

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

SELECT Lat, Long FROM Table   
WHERE (Lat BETWEEN 38 AND 42)   
  AND (Long BETWEEN 138 AND 142)  
  AND ((Lat - 40) * (Lat - 40)) + ((Long - 140) * (Long - 140))  < 4

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

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

1 голос
/ 26 января 2009

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

В очень хорошей статье приведены подробные сведения о некоторых способах решения этой проблемы, но реальный факт состоит в том, что в диалекте SQL Server TSQL отсутствует способ создания скалярной или детерминированной функции (как это делает Oracle).

0 голосов
/ 22 декабря 2008

Обновление:

GMastros: Вы были абсолютно правы. Выполнение математических операций в самом запросе бесконечно быстрее, чем в UDF. Я использую функцию SQUARE () для умножения, что делает его немного более лаконичным, но производительность такая же.

Тем не менее, выполнение этого способа все еще в два раза медленнее, чем выполнение математических операций в .Net.
Я не могу этого понять, но я пришел к компромиссу, который полезен для моей конкретной ситуации (который отстой, потому что мне нужно дублировать код, но это лучший сценарий, если мы не можем найти способ сделать круг расчет в SQL будет быстрее)

Спасибо!

...