для повышения производительности SQL Server могу ли я использовать что-то вроде set-based SQL с INSERT to temp-table?

#sql #sql-server #performance #tsql

#sql #sql-server #Производительность #tsql

Вопрос:

Использование SQL Server 2008 R2

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

Исходя из показанного кода — каким был бы наилучший способ повысить производительность этого узкого места?

 USE [DB]
GO
SET ANSI_NULLS ON
GO
SET QUOTED_IDENTIFIER ON
GO
ALTER FUNCTION [dbo].[ufnGetForUser] (@UserName VARCHAR(100))
RETURNS @User TABLE (
    ID INT
    ,Name VARCHAR(40)
    ,ParentID INT
    ,CompanyID INT
    ,CompanyName VARCHAR(100)
    ,CompanyDisplayName VARCHAR(100)
    ,AnotherID INT
    ,AnotherName VARCHAR(50)
    )
AS
BEGIN
    DECLARE @List TABLE (
        ID INT
        ,Name VARCHAR(40)
        ,ParentID INT
        )

    DECLARE List CURSOR
    FOR
    SELECT ID
    FROM YourTable WITH (NOLOCK)
    WHERE CompanyID IN (
            SELECT companyid
            FROM UserToCompany
            WHERE UserName = @UserName
            )

    DECLARE @ID AS INT

    OPEN List

    FETCH List
    INTO @id

    WHILE (@@FETCH_STATUS = 0)
    BEGIN
        INSERT INTO @List
        SELECT *
        FROM ufnIncludeWithChildren(@ID)

        FETCH List
        INTO @ID
    END

    CLOSE List

    DEALLOCATE List

    INSERT INTO @User
    SELECT DISTINCT a.ID
        ,a.Name
        ,a.ParentID
        ,c.CompanyID
        ,c.CompanyName
        ,c.CompanyDisplayName
        ,d.AnotherID
        ,d.AnotherName
    FROM @List a
    JOIN YourTable b ON a.ID = b.ID
    JOIN CompanyInfo c ON b.CompanyID = c.CompanyID
    JOIN Anothers d ON b.AnotherID = d.AnotherID

    RETURN
END
  

Комментарии:

1. О боже… Первое правило — никогда не использовать курсоры. Тот, что в вашей процедуре, можно легко заменить циклом WHILE. Я посмотрю, смогу ли я это оптимизировать.

2. @Ruslan и второе правило — используйте FAST_FORWARD, если все, что вы делаете, это зацикливание

3. @Conraddfrix Это круто, приятно знать! Однако я стараюсь избегать их вообще, когда это возможно. До этого дня я никогда не видел случая использования, когда их нельзя было бы заменить другой конструкцией (даже в действительно сложных процедурах с 10k LOC).

Ответ №1:

Вы должны иметь возможность использовать один select.

 USE [DB]
GO
SET ANSI_NULLS ON
GO
SET QUOTED_IDENTIFIER ON
GO
ALTER FUNCTION [dbo].[ufnGetForUser] (@UserName VARCHAR(100))
RETURNS @User TABLE (
    ID INT
    ,Name VARCHAR(40)
    ,ParentID INT
    ,CompanyID INT
    ,CompanyName VARCHAR(100)
    ,CompanyDisplayName VARCHAR(100)
    ,AnotherID INT
    ,AnotherName VARCHAR(50)
    )
AS
BEGIN
    INSERT INTO @User
        SELECT DISTINCT a.ID
            ,a.Name
            ,a.ParentID
            ,c.CompanyID
            ,c.CompanyName
            ,c.CompanyDisplayName
            ,d.AnotherID
            ,d.AnotherName
        FROM UserToCompany u
        JOIN CompanyInfo c ON u.CompanyID = c.CompanyID 
        CROSS APPLY ufnIncludeWithChildren(yt.ID) a
        JOIN YourTable b ON a.ID = b.ID
        JOIN Anothers d ON b.AnotherID = d.AnotherID
        WHERE u.UserName = @UserName
    RETURN
END
  

Комментарии:

1. Спасибо! Пробуем это сейчас. Одна из проблем заключается в том, что нет c. ID итак, я пытаюсь понять, как вы это сделали.

2. @JoJo Хорошо, я понял. У этой таблицы изначально отсутствовало название. Я думаю, что обновление к моему ответу решает эту проблему.

3. Отлично! Я думаю, что соединение <TABLE> b На yt. ID = b.ID не представляет таблицу?

4. Привет! Выполнение запроса Select DISTINCT с объединением таблиц заняло 4 минуты.

5. @JoJo Именно с этого вам нужно начать просмотр планов выполнения. Я не вижу ничего неэффективного в моем SQL, поэтому без SQL-скрипта с образцами данных я не уверен, насколько еще я мог бы помочь.

Ответ №2:

На первый взгляд, мы определенно можем исключить CURSOR . Хотя замена его WHILE циклом, как в моем комментарии, является одним из решений, мы можем пойти еще дальше и полностью исключить цикл, используя APPLY предложение для вызова функции непосредственно для каждого значения в строке.

 ALTER FUNCTION [dbo].[ufnGetForUser] (@UserName VARCHAR(100))
RETURNS @User TABLE (
    ID INT
    ,Name VARCHAR(40)
    ,ParentID INT
    ,CompanyID INT
    ,CompanyName VARCHAR(100)
    ,CompanyDisplayName VARCHAR(100)
    ,AnotherID INT
    ,AnotherName VARCHAR(50)
    )
AS
BEGIN
    DECLARE @List TABLE (
        ID INT
        ,Name VARCHAR(40)
        ,ParentID INT
        )

    INSERT INTO @List
    SELECT sfn.*
    FROM YourTable yt WITH (NOLOCK)
    CROSS APPLY ufnIncludeWithChildren(yt.ID) sfn
    WHERE yt.CompanyID IN (
            SELECT companyid
            FROM UserToCompany
            WHERE UserName = @UserName
    )

    INSERT INTO @User
    SELECT DISTINCT a.ID
        ,a.Name
        ,a.ParentID
        ,c.CompanyID
        ,c.CompanyName
        ,c.CompanyDisplayName
        ,d.AnotherID
        ,d.AnotherName
    FROM @List a
    JOIN  b ON a.ID = b.ID
    JOIN CompanyInfo c ON b.CompanyID = c.CompanyID
    JOIN Anothers d ON b.AnotherID = d.AnotherID

    RETURN
END
  

Этот код не тестировался, но должен работать. Пожалуйста, дайте мне знать, если у вас возникнут какие-либо проблемы.

Кроме того, в вашем вопросе вы опустили имя таблицы из первого запроса (где у вас был курсор), поэтому я вставил YourTable туда в качестве заполнителя.

Комментарии:

1. Отлично! Пожалуйста, сделайте. APPLY — это способ выполнения функции для каждой строки результата; это похоже на использование подзапроса, но во многих случаях в несколько раз быстрее (я видел увеличение производительности в 1000 раз), потому что это позволяет SQL Server лучше оптимизировать его. Существует ПЕРЕКРЕСТНОЕ ПРИМЕНЕНИЕ (исключение строк, которые не возвращают результат) и ВНЕШНЕЕ ПРИМЕНЕНИЕ (как ЛЕВОЕ СОЕДИНЕНИЕ, но против функции).

2. Ответ @SpectralGhost еще лучше, потому что он делает еще один шаг вперед и полностью устраняет временную таблицу ‘List’. В остальном это та же оптимизация, но сделан еще один шаг вперед. Таким образом, его решение должно быть еще быстрее

3. О боже!!.. почти мгновенно теперь в веб-приложении!! Мне есть ЧЕМУ поучиться только на одном вашем примере. Я сам выполняю базовые операции CRUD, поэтому я все еще учусь, и это не моя оригинальная функция UFN.

4. @JoJo Да, оптимизация SQL на самом деле очень увлекательна, как только вы в нее вникнете. На одной из моих старых (с большим объемом SQL) заданий у нас на экране была панель мониторинга со средним временем выполнения для всех хранимых процедур в системе (десятки тысяч), и разработчики получали вознаграждение за оптимизацию самых длительных 🙂 Это становится второй натурой, как только вы оптимизируете несколько!

5. @JoJo Сколько данных возвращается? Конечно, все зависит от вашего конкретного варианта использования и т.д., Но ничто не должно занимать 14 секунд, если это не огромный объем данных… Вы просмотрели планы выполнения, чтобы убедиться, что все индексы на месте?