보기에서 *를 선택하면 4 분이 걸립니다


11

뷰에 대해 쿼리를 실행할 때 4 분 이상 걸리는 문제가 발생했습니다. 그러나 쿼리의 내장을 실행하면 1 초 안에 완료됩니다.

내가 확실하지 않은 유일한 것은 조인되는 테이블이 모두 임시 테이블이라는 것입니다.

임시 쿼리 계획 : https://www.brentozar.com/pastetheplan/?id=BykohB2p4

쿼리 계획보기 : https://www.brentozar.com/pastetheplan/?id=SkIfTHh6E

어디에서 시도하고 알아낼 수 있는지에 대한 제안 사항이 있습니까?

코드보기 :

ALTER VIEW [dbo].[vwDealHistoryPITA]
AS
SELECT ROW_NUMBER() OVER (PARTITION BY cm.CodeMasterID ORDER BY cm.CodeMasterID, cm.LastUpdateDate) AS Deal_HistoryID,
       cm.CodeMasterID,
       cm.ProjectName,
       cm.[Status],
       d.CompanyID,
       d.DealTypeMasterID,
       cm.[Description],
       d.PassiveInd,
       d.ApproxTPGOwnership,
       d.NumberBoardSeats,
       d.FollowonInvestmentInd,
       d.SocialImpactInd,
       d.EquityInd,
       d.DebtInd,
       d.RealEstateInd,
       d.TargetPctgReturn,
       d.ApproxTotalDealSize,
       cm.CurrencyCode,
       d.ConflictCheck,
       cm.CreatedDate,
       cm.CreatedBy,
       cm.LastUpdateDate,
       cm.LastUpdateBy,
       d.ExpensesExceedThresholdDate,
       d.CurrentTPGCheckSize,
       d.PreferredEquityInd,
       d.ConvertibleDebtInd,
       d.OtherRealAssetsInd,
       d.InitialTPGCheckSize,
       d.DirectLendingInd,
       cm.NameApproved,
       cm.FolderID,
       cm.CodaProcessedDateTime,
       cm.DeadDate,
       d.SectorMasterID,
       d.DTODataCompleteDate,
       cm.ValidFrom AS CodeMasterValidFrom,
       cm.ValidTo   AS CodeMasterValidTo,
       d.validFrom  AS DealValidFrom,
       d.validTo    AS DealValidTo
FROM dbo.CodeMaster FOR SYSTEM_TIME ALL cm 
INNER JOIN dbo.Deal FOR SYSTEM_TIME ALL d ON cm.CodeMasterID = d.CodeMasterID;
GO

파티션 기준을 추가하고 임시 쿼리와 비슷한 결과를 얻습니다.

답변:


18

주요 성능 차이

여기서 주요 차이점은 쿼리 성능이 향상 CodeMasterID되면 뷰에서 선택이 끝날 때까지 (필터 연산자) 보이지 않는 4 개의 모든 테이블 (2 개의 임시 테이블 (실제 및 히스토리))에서 탐색 술어를 푸시 다운 한다는 것입니다 .

TL DR;

문제는 뷰와 같은 특정 경우에 매개 변수가 창 기능으로 아래로 밀리지 않기 때문입니다. 가장 쉬운 해결책은 OPTION(RECOMPILE)옵티마이 저가 가능한 경우 런타임에 매개 변수를 '볼'수 있도록 뷰 호출에 추가 하는 것입니다. 각 쿼리 호출에 대한 실행 계획을 다시 컴파일하기에 너무 비싸면 매개 변수가 솔루션이 될 수있는 인라인 테이블 값 함수를 사용하십시오. 훌륭한이 블로그 게시물 에 의해 폴 화이트 이것에가. 특정 문제를 찾아서 해결하는 자세한 방법은 계속 읽으십시오.


실적이 우수한 검색어

코드 마스터 테이블

여기에 이미지 설명을 입력하십시오

여기에 이미지 설명을 입력하십시오

거래 테이블

여기에 이미지 설명을 입력하십시오

여기에 이미지 설명을 입력하십시오

아침에 술어 찾기 냄새가 좋아


큰 나쁜 질문

코드 마스터 테이블

여기에 이미지 설명을 입력하십시오

여기에 이미지 설명을 입력하십시오

이것은 술어 전용 영역입니다

거래 테이블

여기에 이미지 설명을 입력하십시오

그러나 옵티마이 저는 'The art of the deal ™'을 읽지 않았습니다.

여기에 이미지 설명을 입력하십시오

... 과거에서 배우지 않는다

모든 데이터가 필터 연산자에 도달 할 때까지

여기에 이미지 설명을 입력하십시오


그래서 무엇을 제공합니까?

여기서 주요 문제는 뷰의 창 기능으로 인해 런타임에 매개 변수를 '보지'않는 옵티 마이저입니다 SelOnSeqPrj (시퀀스 프로젝트에서 선택하십시오 .

테스트 샘플을 사용 SP_EXECUTESQL하여 뷰에 대한 호출을 매개 변수화하는 데 동일한 결과를 복제 할 수있었습니다 . DDL / DML에 대한 부록 참조

윈도우 함수와 테스트 뷰로 테스트 뷰에 대한 쿼리 실행 INNER JOIN

SET STATISTICS IO, TIME ON;
EXEC SP_EXECUTESQL
N'SELECT * FROM  dbo.Bad
Where CodeMasterID = @P1',N'@P1 INT',@P1 = 37155;

약 4.5 초의 CPU 시간과 3.2 초의 경과 시간

 SQL Server Execution Times:
   CPU time = 4595 ms,  elapsed time = 3209 ms.

우리가 달콤한 포옹을 추가하면 OPTION(RECOMPILE)

SET STATISTICS IO, TIME ON;
EXEC SP_EXECUTESQL
N'SELECT * FROM  dbo.Bad
Where CodeMasterID = @P1 OPTION(RECOMPILE)',N'@P1 INT',@P1 = 37155; 

모두 좋다.

 SQL Server Execution Times:
   CPU time = 16 ms,  elapsed time = 98 ms.

이것은 @P1필터 함수를 초래하는 창 함수 및 매개 변수화로 인해 테이블에 술어를 적용 할 수 없다는 점을 다시 지원합니다.

여기에 이미지 설명을 입력하십시오 여기에 이미지 설명을 입력하십시오

임시 테이블의 문제뿐만 아니라

부록 2 참조

임시 테이블을 사용하지 않는 경우에도 다음이 발생합니다. 여기에 이미지 설명을 입력하십시오

다음과 같이 쿼리를 작성할 때도 동일한 결과가 나타납니다.

DECLARE @P1 int = 37155
SELECT * FROM  dbo.Bad2
Where CodeMasterID = @P1;

다시, 옵티마이 저는 창 기능을 적용하기 전에 술어를 내리지 않습니다.

ROW_NUMBER ()를 생략 할 때

CREATE VIEW dbo.Bad3
as
SELECT
cm.CodeMasterID,CM.ManagerID,cm.ParentDeptID,d.DealID, d.CodeMasterID as dealcodemaster,d.EvenMoreBlaID
FROM dbo.CodeMaster2  cm 
INNER JOIN dbo.Deal2  d ON cm.CodeMasterID = d.CodeMasterID;

다 잘

SET STATISTICS IO, TIME ON;
EXEC SP_EXECUTESQL
N'SELECT * FROM  dbo.Bad3
Where CodeMasterID = @P1',N'@P1 INT',@P1 = 37155


 SQL Server Execution Times:
   CPU time = 0 ms,  elapsed time = 33 ms.

그래서 우리를 떠나는 모든 것이 어디입니까?

ROW_NUMBER()필터가 나쁜 쿼리에 적용되기 전에 계산됩니다.

이 모든 것이 창 기능과 뷰에 관한 Paul White의 2013 년 블로그 포스트로 연결 됩니다.

이 예에서 중요한 부분 중 하나는 다음과 같습니다.

불행하게도 SelOnSeqPrj 단순화 규칙은 술어가 상수와 비교를 수행 할 때만 작동합니다. 이러한 이유로 다음 쿼리는 SQL Server 2008 이상에서 차선책을 생성합니다.

DECLARE @ProductID INT = 878;

SELECT
    mrt.ProductID,
    mrt.TransactionID,
    mrt.ReferenceOrderID,
    mrt.TransactionDate,
    mrt.Quantity
FROM dbo.MostRecentTransactionsPerProduct AS mrt 
WHERE
    mrt.ProductID = @ProductID;

여기에 이미지 설명을 입력하십시오

이 부분은 매개 변수를 선언 SP_EXECUTESQL하거나 뷰에서 사용할 때 본 내용 에 해당합니다.


실제 솔루션

1 : 옵션 (권장)

우리 OPTION(RECOMPILE)는 런타임에 가치를 '볼'가능성이 있음 을 알고 있습니다. 각 쿼리 호출에 대한 실행 계획을 다시 컴파일하는 것이 너무 비싸면 다른 솔루션이 있습니다.

2 : 매개 변수가있는 인라인 테이블 값 함수

CREATE FUNCTION dbo.BlaBla
(
    @P1 INT
)  
RETURNS TABLE
WITH SCHEMABINDING AS
RETURN
    (
     SELECT 
     ROW_NUMBER() OVER (PARTITION BY cm.CodeMasterID ORDER BY cm.CodeMasterID) AS Deal_HistoryID,
     cm.CodeMasterID,CM.ManagerID,
     cm.ParentDeptID,d.DealID,
     d.CodeMasterID as dealcodemaster,
     d.EvenMoreBlaID
    FROM dbo.CodeMaster2  cm 
    INNER JOIN dbo.Deal2  d ON cm.CodeMasterID = d.CodeMasterID
    Where cm.CodeMasterID = @P1
    ) 
EXEC SP_EXECUTESQL
N'SELECT * FROM  dbo.BlaBLa(@P1)',N'@P1 INT',@P1 = 37155

예상 탐색 술어 결과

     SQL Server Execution Times:
       CPU time = 0 ms,  elapsed time = 0 ms.

내 테스트에서 약 9 개의 논리적 읽기로

3 :보기를 사용하지 않고 쿼리 작성

다른 '솔루션'은 뷰를 사용하지 않고 쿼리를 완전히 작성할 수 있습니다.

4 : ROW_NUMBER()뷰 에서 함수를 유지하지 않고 뷰 호출에서 함수를 지정하십시오.

이에 대한 예는 다음과 같습니다.

CREATE VIEW dbo.Bad2
as
SELECT 
cm.CodeMasterID,CM.ManagerID,cm.ParentDeptID,d.DealID, d.CodeMasterID as dealcodemaster,d.EvenMoreBlaID
FROM dbo.CodeMaster2  cm 
INNER JOIN dbo.Deal2  d ON cm.CodeMasterID = d.CodeMasterID;

GO
SET STATISTICS IO, TIME ON;
EXEC SP_EXECUTESQL
N'SELECT ROW_NUMBER() OVER (PARTITION BY CodeMasterID ORDER BY CodeMasterID) AS Deal_HistoryID,* FROM  dbo.Bad2
Where CodeMasterID = @P1',N'@P1 INT',@P1 = 37155;

이 문제와 관련하여 다른 창의적인 방법이 있어야합니다. 중요한 부분은 문제의 원인을 아는 것입니다.


부록 # 1

CREATE TABLE dbo.Codemaster   
(    
     CodeMasterID int NOT NULL PRIMARY KEY CLUSTERED  
   , ManagerID INT  NULL  
   , ParentDeptID int NULL  
   , SysStartTime datetime2 GENERATED ALWAYS AS ROW START NOT NULL  
   , SysEndTime datetime2 GENERATED ALWAYS AS ROW END NOT NULL  
   , PERIOD FOR SYSTEM_TIME (SysStartTime,SysEndTime)     
)    
WITH (SYSTEM_VERSIONING = ON (HISTORY_TABLE = dbo.Codemaster_History))   
;  

CREATE TABLE dbo.Deal   
(    
     DealID int NOT NULL PRIMARY KEY CLUSTERED  
   , CodeMasterID INT  NULL  
   , EvenMoreBlaID int NULL  
   , SysStartTime datetime2 GENERATED ALWAYS AS ROW START NOT NULL  
   , SysEndTime datetime2 GENERATED ALWAYS AS ROW END NOT NULL  
   , PERIOD FOR SYSTEM_TIME (SysStartTime,SysEndTime)     
)    
WITH (SYSTEM_VERSIONING = ON (HISTORY_TABLE = dbo.Deal_History))   
;  

INSERT INTO dbo.Codemaster(CodeMasterID,ManagerID,ParentDeptID)
SELECT TOP(1000000) ROW_NUMBER() OVER(ORDER BY(SELECT NULL)) as rownum1,
                    ROW_NUMBER() OVER(ORDER BY(SELECT NULL)) as rownum2,
                    ROW_NUMBER() OVER(ORDER BY(SELECT NULL)) as rownum3
FROM MASTER..spt_values as spt1
CROSS JOIN MASTER..spt_values as spt2;


INSERT INTO dbo.Deal(DealID,CodeMasterID,EvenMoreBlaID)
SELECT TOP(1000000) ROW_NUMBER() OVER(ORDER BY(SELECT NULL)) as rownum1,
                    ROW_NUMBER() OVER(ORDER BY(SELECT NULL)) as rownum2,
                    ROW_NUMBER() OVER(ORDER BY(SELECT NULL)) as rownum3
FROM MASTER..spt_values as spt1
CROSS JOIN MASTER..spt_values as spt2;

CREATE INDEX IX_CodeMasterID
ON dbo.Deal(CodeMasterId);
CREATE INDEX IX_CodeMasterID
ON dbo.Deal_History(CodeMasterId);

CREATE INDEX IX_CodeMasterID
ON dbo.Codemaster(CodeMasterId);
CREATE INDEX IX_CodeMasterID
ON dbo.Codemaster_History(CodeMasterId);


SELECT ROW_NUMBER() OVER (PARTITION BY cm.CodeMasterID ORDER BY cm.CodeMasterID, cm.SysStartTime) AS Deal_HistoryID,
cm.*, d.* 
FROM dbo.CodeMaster FOR SYSTEM_TIME ALL cm 
INNER JOIN dbo.Deal FOR SYSTEM_TIME ALL d ON cm.CodeMasterID = d.CodeMasterID
Where cm.CodeMasterID = 37155;

-- Guud
GO
CREATE VIEW dbo.Bad
as
SELECT ROW_NUMBER() OVER (PARTITION BY cm.CodeMasterID ORDER BY cm.CodeMasterID, cm.SysStartTime) AS Deal_HistoryID,
cm.CodeMasterID,CM.ManagerID,cm.ParentDeptID,d.DealID, d.CodeMasterID as dealcodemaster,d.EvenMoreBlaID
FROM dbo.CodeMaster FOR SYSTEM_TIME ALL cm 
INNER JOIN dbo.Deal FOR SYSTEM_TIME ALL d ON cm.CodeMasterID = d.CodeMasterID

GO
EXEC SP_EXECUTESQL
N'SELECT * FROM  dbo.Bad
Where CodeMasterID = @P1',N'@P1 INT',@P1 = 37155

-- Very bad shame on you

부록 # 2

CREATE TABLE dbo.Codemaster2
(    
     CodeMasterID int NOT NULL PRIMARY KEY CLUSTERED  
   , ManagerID INT  NULL  
   , ParentDeptID int NULL  

);  

CREATE TABLE dbo.Deal2
(    
     DealID int NOT NULL PRIMARY KEY CLUSTERED  
   , CodeMasterID INT  NULL  
   , EvenMoreBlaID int NULL    
);  

INSERT INTO dbo.Codemaster2(CodeMasterID,ManagerID,ParentDeptID)
SELECT TOP(1000000) ROW_NUMBER() OVER(ORDER BY(SELECT NULL)) as rownum1,
                    ROW_NUMBER() OVER(ORDER BY(SELECT NULL)) as rownum2,
                    ROW_NUMBER() OVER(ORDER BY(SELECT NULL)) as rownum3
FROM MASTER..spt_values as spt1
CROSS JOIN MASTER..spt_values as spt2;


INSERT INTO dbo.Deal2(DealID,CodeMasterID,EvenMoreBlaID)
SELECT TOP(1000000) ROW_NUMBER() OVER(ORDER BY(SELECT NULL)) as rownum1,
                    ROW_NUMBER() OVER(ORDER BY(SELECT NULL)) as rownum2,
                    ROW_NUMBER() OVER(ORDER BY(SELECT NULL)) as rownum3
FROM MASTER..spt_values as spt1
CROSS JOIN MASTER..spt_values as spt2;

CREATE INDEX IX_CodeMasterID
ON dbo.Deal2(CodeMasterId);
CREATE INDEX IX_CodeMasterID
ON dbo.Codemaster2(CodeMasterId);


SELECT ROW_NUMBER() OVER (PARTITION BY cm.CodeMasterID ORDER BY cm.CodeMasterId) AS Deal_HistoryID,
cm.*, d.* 
FROM dbo.CodeMaster2 cm 
INNER JOIN dbo.Deal2 d ON cm.CodeMasterID = d.CodeMasterID
Where cm.CodeMasterID = 37155;

-- Guud
GO
CREATE VIEW dbo.Bad2
as
SELECT ROW_NUMBER() OVER (PARTITION BY cm.CodeMasterID ORDER BY cm.CodeMasterID) AS Deal_HistoryID,
cm.CodeMasterID,CM.ManagerID,cm.ParentDeptID,d.DealID, d.CodeMasterID as dealcodemaster,d.EvenMoreBlaID
FROM dbo.CodeMaster2  cm 
INNER JOIN dbo.Deal2  d ON cm.CodeMasterID = d.CodeMasterID

GO
SET STATISTICS IO, TIME ON;
EXEC SP_EXECUTESQL
N'SELECT * FROM  dbo.Bad2
Where CodeMasterID = @P1',N'@P1 INT',@P1 = 37155
당사 사이트를 사용함과 동시에 당사의 쿠키 정책개인정보 보호정책을 읽고 이해하였음을 인정하는 것으로 간주합니다.
Licensed under cc by-sa 3.0 with attribution required.