SQL Server 2017 Enterprise Editionのインスタンスで、ストアドプロシージャが約1週間かかっていました。実行に5分。ストアドプロシージャのコードを確認した後、SELECTリストとストアドプロシージャの本体の述語WHERE句で数回参照されているインラインスカラーUDFがあることがわかりました。
コードを所有しているアプリケーションチームに、オンボードで取得してTVFで置き換えたインラインUDFを使用しないようにストアドプロシージャをリファクタリングするようにアドバイスしました。その間、アプリケーションデータベースにはデータベース互換性レベル100がまだあることに気付いたので、Data Migration Assistantを介してデータベースを実行し、非推奨の機能や重大な変更がないか確認した後、これを最新レベルの140に上げました。
TVFのUDFを交換し、データベースの互換性レベルを100から140に上げると、パフォーマンスが大幅に向上し、ストアドプロシージャが1分以内に実行されるようになりましたが、パフォーマンスはまだ望んでいません。誰かが私が欠けている明らかなことについてアドバイスしたり、コードをさらに最適化したり、これをよりよく実行するためにできる他のことの正しい方向に私を向けたりできるといいのですが?実行計画はこちら: https://www.brentozar.com/pastetheplan/?id=ByrsEdRpr
ストアドプロシージャと関数のコードは次のとおりであり、ストアドプロシージャは次のようにアプリケーションによって呼び出されます。 "EXEC dbo.CAOT_GetApplicationQueue; 1"
/****** Object: StoredProcedure [dbo].[CAOT_GetApplicationQueue] ******/
SET ANSI_NULLS ON
GO
SET QUOTED_IDENTIFIER ON
GO
CREATE PROCEDURE [dbo].[CAOT_GetApplicationQueue]
(@userID VARCHAR(50)='', @showComplete CHAR(1)='N', @JustMyQueue BIT=0, @ChannelId VARCHAR(10) = NULL )
AS
BEGIN
SELECT App.pkApplication ,
COALESCE(ApplicationReference, AlternateApplicationReference) AS ApplicationReference ,
ApplicationDate ,
Name ,
Telephone ,
[Address] ,
Email ,
CIN ,
Dob ,
CreatedDate ,
BusinessPhone ,
PostCode ,
MobilePhone ,
[Action] ,
ActionStatus ,
branchNumber ,
AccountNumber ,
AccountType ,
act.accountDescription,
IsNull( appstatus.DESCRIPTION ,'-- CREATED --') As LastStatus,
IsNull(appstatus.DAYS,'0') DaysSinceLastStatus ,
DATEDIFF(d,ApplicationDate, GETDATE()) DaysCreated,
InitialUserID,
IsNull(appstatus.STATUS,'-- MADE --') APPLICATIONSTATUS
FROM dbo.CAOT_Application (NOLOCK) app
LEFT OUTER JOIN dbo.CAOT_AccountType (NOLOCK) act
ON app.AccountType = act.AccountTypecode
LEFT OUTER JOIN [CAOT_GetAllApplicationStatus]() appstatus
ON app.pkApplication = appstatus.[PKAPPLICATION]
WHERE (IsNull(appstatus.STATUSCODE,'MADE') NOT IN ('CANCELLED','DECLINED','COMPLETE','EXPIRED')
OR @showComplete='Y') AND
(@JustMyQueue = 0 OR InitialUserID = @userID) AND
(@ChannelId IS NULL OR ChannelID = @ChannelId OR (@ChannelId = 'CBU' AND ChannelID IS NULL AND isCAO='N'))
ORDER BY CASE WHEN InitialUserID = @userid THEN 10 ELSE 900 END, ApplicationDate DESC
END
GO
/****** Object: UserDefinedFunction [dbo].[CAOT_GetAllApplicationStatus] ******/
SET ANSI_NULLS ON
GO
SET QUOTED_IDENTIFIER ON
GO
CREATE FUNCTION [dbo].[CAOT_GetAllApplicationStatus]() RETURNS
@return TABLE
(
[PKAPPLICATION] [int] NOT NULL,
[PKAPPLICATIONEVENT] INT,
[EVENTCREATEDDATE] [DATETIME] NULL,
[KEY] VARCHAR(12) NULL,
[DESCRIPTION] VARCHAR(200) NULL,
[CODE] VARCHAR(20) NULL,
[DAYS] VARCHAR(20) NULL,
[STATUS] VARCHAR(200) NULL,
[STATUSCODE] VARCHAR(50) NULL
)
AS
BEGIN
Declare @AppStatus table
(
[PKAPPLICATION] [int] NOT NULL,
[PKAPPLICATIONEVENT] INT,
[EVENTCREATEDDATE] [DATETIME] NULL,
[KEY] VARCHAR(12) NULL,
[DESCRIPTION] VARCHAR(200) NULL,
[CODE] VARCHAR(20) NULL,
[DAYS] VARCHAR(20) NULL,
[STATUS] VARCHAR(200) NULL,
[STATUSCODE] VARCHAR(50) NULL
)
INSERT INTO @AppStatus
SELECT
fkApplication,
ev.pkApplicationEvent As pkApplicationEvent,
ev.CreateDate As 'EventCreatedDate',
CONVERT(VARCHAR(12), evt.fkApplicationStatus) As 'KEY',
evt.EventDescription As 'DESCRIPTION',
evt.EventCode As 'CODE' ,
CONVERT(VARCHAR(20), DATEDIFF(d, ev.createdate, GETDATE()) ) As 'DAYS',
apps.StatusDescription As 'STATUS' ,
apps.StatusCode As 'STATUSCODE'
FROM dbo.CAOT_ApplicationEvent (NOLOCK) ev
INNER JOIN dbo.CAOT_EventType (NOLOCK) evt ON ev.fkEventType = evt.pkEventType
INNER JOIN dbo.CAOT_ApplicationStatus (NOLOCK) apps ON evt.fkApplicationStatus = apps.pkApplicationStatus
ORDER BY ev.CreateDate DESC, ev.pkApplicationEvent DESC
INSERT INTO @return
Select * from @AppStatus AllStatus
Where AllStatus.EVENTCREATEDDATE = ( Select Max(LatestAppStatus.EVENTCREATEDDATE) from @AppStatus LatestAppStatus where LatestAppStatus.PKAPPLICATION =AllStatus.PKAPPLICATION ) --Z On X.PKAPPLICATION = Z.PKAPPLICATION
RETURN
END
GO
TVFをビューに置き換えることができます(またはTVFは保持しますが、パフォーマンスが重要なsprocにはビューを使用します)。
_CREATE VIEW CAOT_AllApplicationStatuses AS
SELECT
fkApplication,
ev.pkApplicationEvent AS pkApplicationEvent,
ev.CreateDate AS EventCreatedDate,
CONVERT(VARCHAR(12), evt.fkApplicationStatus) As 'KEY',
evt.EventDescription AS 'DESCRIPTION',
evt.EventCode AS 'CODE',
CONVERT(VARCHAR(20), DATEDIFF(d, ev.createdate, GETDATE())) AS 'DAYS',
apps.StatusDescription AS 'STATUS',
apps.StatusCode AS 'STATUSCODE'
FROM
dbo.CAOT_ApplicationEvent (NOLOCK) ev
INNER JOIN dbo.CAOT_EventType (NOLOCK) evt ON ev.fkEventType = evt.pkEventType
INNER JOIN dbo.CAOT_ApplicationStatus (NOLOCK) apps ON evt.fkApplicationStatus = apps.pkApplicationStatus
WHERE
NOT EXISTS
(
SELECT * FROM dbo.CAOT_ApplicationEvent AS LaterEvent WHERE EV.pkApplication = LaterEvent.pkApplication AND LaterEvent.pkApplication.CreateDate > EV.CreateDate
)
ORDER BY
ev.CreateDate DESC, ev.pkApplicationEvent DESC
_
これは、TVFのメインSELECT
クエリの内容であり、2番目のWHERE
のSELECT
句が_NOT EXISTS
_として組み込まれています。 _CAOT_ApplicationEvent
_のすべてのレコードが_CAOT_EventType
_および_CAOT_ApplicationStatus
_にレコードがあることを信頼しています。そうでない場合は、これらの結合を_NOT EXISTS
_クエリに追加する必要があります。
パーサーはビューを最終クエリに組み込み、未使用の部分を破棄するため、TVFではなくビューを使用するだけで問題が解決する場合があります。たとえば、これらのCONVERT()
への呼び出しは比較的高価になる可能性がありますが、使用されていないようです。ただし、最上位のsprocの複雑な述語では、テーブルスキャンが必要になる場合があります。これを試して、さらに作業が必要かどうかを確認してみましょう!
主な懸念事項
(@JustMyQueue = 0 OR InitialUserID = @userID)
。 UNION ALLで変換するか、#tempテーブルとJOINに入れてください。例、
create table #Status(Status varchar(15))
if(@showComplete='Y')
begin
insert into #Status
end
else
begin
insert into #Status
end
#Statusに参加してください
編集1:
UDFでパラメーターを渡した後、パフォーマンスの向上に気づくはずです。それはさらに改善することができます。可能であれば、RETURNS VARCHAR(100)をVARCHAR(50)程度に減らします。
ALTER FUNCTION [dbo].[CAOT_GetApplicationStatus]
(@pkApplication INT, @returnType varchar(20))
RETURNS VARCHAR(100)
AS
--DECLARE @status VARCHAR(100)
RETURN(
SELECT TOP 1
CASE @returntype
WHEN 'KEY' THEN CONVERT(VARCHAR(12), isnull(evt.fkApplicationStatus,'1'))
WHEN 'DESCRIPTION' THEN isnull(evt.EventDescription,'-- CREATED --')
WHEN 'CODE' THEN isnull(evt.EventCode,'CREATE')
WHEN 'DAYS' THEN CONVERT(VARCHAR(20), isnull(DATEDIFF(d, ev.createdate, GETDATE()),'0') )
WHEN 'STATUS' THEN isnull(apps.StatusDescription,'-- MADE --')
WHEN 'STATUSCODE' THEN isnull(apps.StatusCode,'MADE')
ELSE isnull(evt.EventDescription,'')
END
FROM dbo.CAOT_ApplicationEvent (NOLOCK) ev
INNER JOIN dbo.CAOT_EventType (NOLOCK) evt
ON ev.fkEventType = evt.pkEventType
INNER JOIN dbo.CAOT_ApplicationStatus (NOLOCK) apps
ON evt.fkApplicationStatus = apps.pkApplicationStatus
WHERE fkApplication = @pkApplication
ORDER BY ev.CreateDate DESC, ev.pkApplicationEvent DESC
)
GO
#Status結合のトリックを忘れないでください。スクリプトとクエリプランを更新する必要があります。
読んだ後 http://sommarskog.se/dyn-search.html
次のように、クエリを複数の部分に分割できます。
create procedure [dbo].[CAOT_GetApplicationQueue] ( @userID varchar(50) = '', @showComplete char(1) = 'N', @JustMyQueue bit = 0, @ChannelId varchar(10) = null )
as begin
select case when InitialUserID = @userID then 10 else 900 end as SortCol, app.pkApplication, coalesce(ApplicationReference, AlternateApplicationReference) as ApplicationReference, ApplicationDate, Name, Telephone, [Address], Email, CIN, Dob, CreatedDate, BusinessPhone, PostCode, MobilePhone, [Action], ActionStatus, branchNumber, AccountNumber, AccountType
, act.accountDescription, isnull(appstatus.DESCRIPTION, '-- CREATED --') as LastStatus, isnull(appstatus.DAYS, '0') DaysSinceLastStatus, datediff(d, ApplicationDate, getdate()) DaysCreated, InitialUserID, isnull(appstatus.STATUS, '-- MADE --') APPLICATIONSTATUS
from dbo.CAOT_Application app
left outer join dbo.CAOT_AccountType act on app.AccountType = act.AccountTypecode
left outer join [CAOT_GetAllApplicationStatus]() appstatus on app.pkApplication = appstatus.[PKAPPLICATION]
where ( isnull(appstatus.STATUSCODE, 'MADE') not in ( 'CANCELLED', 'DECLINED', 'COMPLETE', 'EXPIRED' ) or @showComplete = 'Y' )
and ( @JustMyQueue = 0 /* or InitialUserID = @userID */)
and ( @ChannelId is null or ChannelID = @ChannelId or ( @ChannelId = 'CBU' and ChannelID is null and isCAO = 'N' ))
union all
select 10 as SortCol, app.pkApplication, coalesce(ApplicationReference, AlternateApplicationReference) as ApplicationReference, ApplicationDate, Name, Telephone, [Address], Email, CIN, Dob, CreatedDate, BusinessPhone, PostCode, MobilePhone, [Action], ActionStatus, branchNumber, AccountNumber, AccountType
, act.accountDescription, isnull(appstatus.DESCRIPTION, '-- CREATED --') as LastStatus, isnull(appstatus.DAYS, '0') DaysSinceLastStatus, datediff(d, ApplicationDate, getdate()) DaysCreated, InitialUserID, isnull(appstatus.STATUS, '-- MADE --') APPLICATIONSTATUS
from dbo.CAOT_Application app
left outer join dbo.CAOT_AccountType act on app.AccountType = act.AccountTypecode
left outer join [CAOT_GetAllApplicationStatus]() appstatus on app.pkApplication = appstatus.[PKAPPLICATION]
where ( isnull(appstatus.STATUSCODE, 'MADE') not in ( 'CANCELLED', 'DECLINED', 'COMPLETE', 'EXPIRED' ) or @showComplete = 'Y' )
and ( @JustMyQueue <> 0)
and ( InitialUserID = @userID)
and ( @ChannelId is null or ChannelID = @ChannelId or ( @ChannelId = 'CBU' and ChannelID is null and isCAO = 'N' ))
order by SortCol, ApplicationDate desc
option (recompile)
end;
CAOT_Applicationテーブルの検索する行が少なくなる可能性があります。