Скорость выполнения SQL Server сильно варьируется в зависимости от того, как параметры предоставляются для встроенной табличной функции - PullRequest
0 голосов
/ 30 сентября 2019

Я исследую проблему со скоростью выполнения встроенной табличной функции в SQL Server. Или я подумал, что проблема в этом. Я сталкивался с

Код T-SQL очень медленный при сохранении в виде встроенной табличной функции

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

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

SELECT strStudentNumber
FROM dbo.udfNominalSnapshot('2019', 'REG')

занимает 17 секунд, тогда как

DECLARE @strAcademicSessionStart varchar(4) = '2019'
DECLARE  @strProgressCode varchar(12)= 'REG'

SELECT strStudentNumber
FROM dbo.udfNominalSnapshot(@strAcademicSessionStart, @strProgressCode)

занимает миллисекунды! Так что нет ничего общего с упаковкой кода во встроенную табличную функцию, но все, что связано с передачей параметров вложенной функции внутри нее. Основываясь на цитируемой статье, я предполагаю, что в игре есть два разных плана выполнения, но я понятия не имею, почему / как, и что более важно, что я могу сделать, чтобы убедить SQL Server использовать эффективный?

PS вот код внутреннего вызова UDF в ответ на запрос комментария

ALTER FUNCTION [dbo].[udfNominalSnapshot] 
(   
    @strAcademicSessionStart varchar(4)='%',
        @strProgressCode varchar(10)='%'
)
RETURNS TABLE 
AS
RETURN 
(
SELECT     TOP 100 PERCENT S.strStudentNumber, S.strSurname, S.strForenames, S.strTitle, S.strPreviousSurname, S.dtmDoB, S.strGender, S.strMaritalStatus, 
                      S.strResidencyCode, S.strNationalityCode, S.strHESAnumber, S.strSLCnumber, S.strPreviousSchoolName, S.strPreviousSchoolCode, 
                      S.strPreviousSchoolType, 
                      COLLEGE_EMAIL.strEmailAddress AS strEmailAlias, 
                      PERSONAL_EMAIL.strEmailAddress AS strPersonalEmail,
                      P.[str(Sub)Plan], P.intYearOfCourse, P.strProgressCode, 
                      P.strAcademicSessionStart, strC2Knumber AS C2K_ID, AcadPlan,  strC2KmailAlias
                      ,ISNULL([strC2KmailAlias], [strC2Knumber]) + '@c2kni.net' AS strC2KmailAddress

FROM         dbo.tblStudents AS S
                      LEFT JOIN
                      dbo.udfMostRecentEmail('COLLEGE') AS COLLEGE_EMAIL ON S.strStudentNumber = COLLEGE_EMAIL.strStudentNumber
                      LEFT JOIN
                      dbo.udfMostRecentEmail('PERSONAL') AS PERSONAL_EMAIL ON S.strStudentNumber = PERSONAL_EMAIL.strStudentNumber
                       INNER JOIN
                      dbo.udfProgressHistory(@strAcademicSessionStart) AS P ON S.strStudentNumber = P.strStudentNumber
WHERE     (P.strProgressCode LIKE @strProgressCode OR (SUBSTRING(@strProgressCode, 1, 1) = '^' AND P.strProgressCode NOT LIKE SUBSTRING(@strProgressCode, 2, LEN(@strProgressCode)))) AND 
(P.strStudentNumber NOT IN
                          (SELECT     strStudentNumber
                            FROM          dbo.tblPilgrims
                            WHERE      (strAcademicSessionStart = @strAcademicSessionStart) AND (strScheme = 'BEI')))
ORDER BY P.[str(Sub)Plan], P.intYearOfCourse, S.strSurname
)

Ответы [ 2 ]

2 голосов
/ 01 октября 2019

Расширяя комментарий @Ross Pressers, это может быть не совсем ответ, но демонстрирует, что происходит (немного), с моим пониманием (что может быть неправильно!) Того, что происходит ...

Запустите установочный код в конце и затем ....

Выполните следующее с планом запроса на (Ctrl-M) ... (примечание: в зависимости от генератора случайных чисел вы можете или не можете получитьлюбые результаты, которые не влияют на план)

declare @one varchar(100) = '379', @two varchar(200) = '726'
select * from wibble(@one, @two)  -- 1 
select * from wibble('379', '726') -- 2
select * from wibble(@one, @two) OPTION (RECOMPILE) -- 3
select * from wibble(@one, @two)  -- 4

Предостережение. Вот что происходит в MY системе, ваш пробег может варьироваться ...

- 1 (и - 4) самые дорогие.

SQL Serverсоздает общий план, поскольку он не знает, каковы параметры (да, они определены, но план предназначен для колебания (@one, @two), где в этот момент значения параметров «неизвестны») https://www.brentozar.com/pastetheplan/?id=rJtIRwx_r

- 2 имеет другой план

Здесь сервер sql знает, каковы параметры, поэтому может создать конкретный план, который сильно отличается от --1 https://www.brentozar.com/pastetheplan/?id=rJa9APldS

- 3 имеет тот же план, что и --2

При дальнейшем тестировании добавление OPTION (RECOMPILE) заставляет SQL Server создать конкретный план для конкретного выполнения вибля (@one, @two), поэтомумы получаем тот же план, что и --2

- 4 для полноты, чтобы показать, что после всего этого гадости по поводу общего плана все еще на месте

Итак, в этом простом примереиметь параметризованный TVF, вызываемый с одинаковыми значениями, которые передаются как параметрыили встроенный, производя разные планы выполнения и разное время выполнения согласно OP

Настройка

use tempdb
GO

drop table if EXISTS Orders 
GO


create table Orders (
    OrderID int primary key,
    UserName varchar(50),
    PhoneNumber1 varchar(50),
)

-- generate 300000 with randon "phone" numbers

;WITH TallyTable AS (
SELECT TOP 300000 ROW_NUMBER() OVER(ORDER BY (SELECT NULL)) AS [N]
  FROM dbo.syscolumns tb1,dbo.syscolumns tb2 
)
insert into Orders
select n, 'user' + cast(n as varchar(10)), cast(CRYPT_GEN_RANDOM(3) as int)
FROM TallyTable;


GO
drop function if exists wibble
GO

create or alter function wibble (
    @one varchar(4) = '%'
    , @two varchar(4) = '%'

)
returns table
as
return select * from Orders
where PhoneNumber1 like '%' + @one + '%'
and PhoneNumber1 like '%' + @two + '%'
or (SUBSTRING(@one, 1, 1) = '^' AND PhoneNumber1 NOT LIKE SUBSTRING(@two, 2, LEN(@two)))
and (select 1) = 1

GO
1 голос
/ 07 октября 2019

Проблема была преодолена (я бы не сказал «исправлено»), следуя наблюдению Росса Прессера о сложности udfProgressHistory. Это высосало данные из таблицы tblProgressHistory, которая была присоединена к себе. Таблица добавляется ежегодно. Я думаю, что дополнительные 2К записи этого года, должно быть, вызвали внезапное повышение стоимости при использовании определенного плана выполнения. Я удалил> 2K избыточных записей, и мы вернулись к выполнению менее чем за секунду.

...