성능을 저하시키는 키 조회 (Clustered) 연산자 제거


16

실행 계획에서 키 조회 (클러스터) 연산자를 제거하려면 어떻게해야합니까?

테이블 tblQuotes에는 이미 클러스터형 인덱스 (on QuoteID)와 27 개의 비 클러스터형 인덱스가 있으므로 더 이상 만들지 않습니다.

클러스터 된 인덱스 열 QuoteID을 쿼리에 넣었을 때 도움이되기를 원했지만 불행히도 여전히 동일합니다.

실행 계획은 여기에 있습니다 .

또는보십시오 :

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

키 조회 연산자는 다음과 같이 말합니다.

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

질문:

declare
        @EffDateFrom datetime ='2017-02-01',
        @EffDateTo   datetime ='2017-08-28'

SET NOCOUNT ON
SET TRANSACTION ISOLATION LEVEL READ UNCOMMITTED

IF OBJECT_ID('tempdb..#Data') IS NOT NULL
    DROP TABLE #Data 
CREATE TABLE #Data
(
    QuoteID int NOT NULL,   --clustered index

    [EffectiveDate] [datetime] NULL, --not indexed
    [Submitted] [int] NULL,
    [Quoted] [int] NULL,
    [Bound] [int] NULL,
    [Exonerated] [int] NULL,
    [ProducerLocationId] [int] NULL,
    [ProducerName] [varchar](300) NULL,
    [BusinessType] [varchar](50) NULL,
    [DisplayStatus] [varchar](50) NULL,
    [Agent] [varchar] (50) NULL,
    [ProducerContactGuid] uniqueidentifier NULL
)
INSERT INTO #Data
    SELECT 
        tblQuotes.QuoteID,

          tblQuotes.EffectiveDate,
          CASE WHEN lstQuoteStatus.QuoteStatusID >= 1   THEN 1 ELSE 0 END AS Submitted,
          CASE WHEN lstQuoteStatus.QuoteStatusID = 2 or lstQuoteStatus.QuoteStatusID = 3 or lstQuoteStatus.QuoteStatusID = 202 THEN 1 ELSE 0 END AS Quoted,
          CASE WHEN lstQuoteStatus.Bound = 1 THEN 1 ELSE 0 END AS Bound,
          CASE WHEN lstQuoteStatus.QuoteStatusID = 3 THEN 1 ELSE 0 END AS Exonareted,
          tblQuotes.ProducerLocationID,
          P.Name + ' / '+ P.City as [ProducerName], 
        CASE WHEN tblQuotes.PolicyTypeID = 1 THEN 'New Business' 
             WHEN tblQuotes.PolicyTypeID = 3 THEN 'Rewrite'
             END AS BusinessType,
        tblQuotes.DisplayStatus,
        tblProducerContacts.FName +' '+ tblProducerContacts.LName as Agent,
        tblProducerContacts.ProducerContactGUID
FROM    tblQuotes 
            INNER JOIN lstQuoteStatus 
                on tblQuotes.QuoteStatusID=lstQuoteStatus.QuoteStatusID
            INNER JOIN tblProducerLocations P 
                On P.ProducerLocationID=tblQuotes.ProducerLocationID
            INNER JOIN tblProducerContacts 
                ON dbo.tblQuotes.ProducerContactGuid = tblProducerContacts.ProducerContactGUID

WHERE   DATEDIFF(D,@EffDateFrom,tblQuotes.EffectiveDate)>=0 AND DATEDIFF(D, @EffDateTo, tblQuotes.EffectiveDate) <=0
        AND dbo.tblQuotes.LineGUID = '6E00868B-FFC3-4CA0-876F-CC258F1ED22D'--Surety
        AND tblQuotes.OriginalQuoteGUID is null

select * from #Data

실행 계획 :

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


예상 행과 실제 행은 눈에 띄는 차이를 보여줍니다. 아마도 SQL은 좋은 추정을 할 데이터가 없기 때문에 잘못된 계획을 선택했을 것입니다. 통계는 얼마나 자주 업데이트합니까?
RDFozz 2012 년

답변:


23

쿼리 프로세서가 결과를 반환하는 데 필요한 행을 찾는 데 사용되는 인덱스에 저장되지 않은 열에서 값을 가져와야하는 경우 다양한 특징에 대한 키 조회가 발생합니다.

단일 인덱스로 테이블을 생성하는 다음 코드를 예로 들어 보겠습니다.

USE tempdb;

IF OBJECT_ID(N'dbo.Table1', N'U') IS NOT NULL
DROP TABLE dbo.Table1
GO

CREATE TABLE dbo.Table1
(
    Table1ID int NOT NULL IDENTITY(1,1)
    , Table1Data nvarchar(30) NOT NULL
);

CREATE INDEX IX_Table1
ON dbo.Table1 (Table1ID);
GO

테이블에 1,000,000 개의 행을 삽입하여 작업 할 데이터가 있습니다.

INSERT INTO dbo.Table1 (Table1Data)
SELECT TOP(1000000) LEFT(c.name, 30)
FROM sys.columns c
    CROSS JOIN sys.columns c1
    CROSS JOIN sys.columns c2;
GO

이제 "실제"실행 계획을 표시하는 옵션으로 데이터를 쿼리합니다.

SELECT *
FROM dbo.Table1
WHERE Table1ID = 500000;

쿼리 계획은 다음을 보여줍니다.

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

해당 IX_Table1인덱스를 Table1ID = 5000000보는 것이 해당 값을 찾는 전체 테이블을 스캔하는 것보다 훨씬 빠르므로 쿼리는 인덱스를 보고 행을 찾습니다 . 그러나 쿼리 결과를 만족 시키려면 쿼리 프로세서가 테이블의 다른 열에 대한 값도 찾아야합니다. 이것은 "RID 조회"가 들어오는 곳입니다. 테이블에서 Table1ID500000 값을 포함하는 행과 연관된 행 ID (RID 조회의 RID)를 찾아 Table1Data열 에서 값을 가져옵니다 . 계획에서 "RID 조회"노드 위로 마우스를 가져 가면 다음이 표시됩니다.

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

"출력 목록"에는 RID 조회에서 반환 한 열이 포함됩니다.

클러스터형 인덱스와 비 클러스터형 인덱스가있는 테이블이 흥미로운 예입니다. 아래 표에는 세 개의 열이 있습니다. Dat비 클러스터형 인덱스 IX_Table및 세 번째 열에 의해 인덱싱되는 클러스터링 키인 ID입니다 Oth.

USE tempdb;

IF OBJECT_ID(N'dbo.Table1', N'U') IS NOT NULL
DROP TABLE dbo.Table1
GO

CREATE TABLE dbo.Table1
(
    ID int NOT NULL IDENTITY(1,1) 
        PRIMARY KEY CLUSTERED
    , Dat nvarchar(30) NOT NULL
    , Oth nvarchar(3) NOT NULL
);

CREATE INDEX IX_Table1
ON dbo.Table1 (Dat);
GO

INSERT INTO dbo.Table1 (Dat, Oth)
SELECT TOP(1000000) CRYPT_GEN_RANDOM(30), CRYPT_GEN_RANDOM(3)
FROM sys.columns c
    CROSS JOIN sys.columns c1
    CROSS JOIN sys.columns c2;
GO

이 예제 쿼리를 보자.

SELECT *
FROM dbo.Table1
WHERE Dat = 'Test';

SQL Server에 Dat열에 단어가 포함 된 테이블의 모든 열을 반환하도록 요청하고 있습니다 Test. 여기 몇 가지 선택이 있습니다. 우리는 테이블 (즉, 클러스터 된 인덱스)를 볼 수 있습니다 -하지만 테이블이 발주되기 때문에 전체 일을 스캔 수반 ID우리에게 행 (들)도 포함 된에 대해 아무것도 알 수없는 열, TestDat열을. 다른 옵션 (및 SQL Server에서 선택한 옵션)은 IX_Table1비 클러스터형 인덱스를 찾아 행을 찾는 것으로 구성됩니다 Dat = 'Test'. 그러나 Oth열도 필요하므로 SQL Server는 "Key를 사용하여 클러스터형 인덱스를 조회해야합니다. 조회 "작업. 이것은 그 계획입니다 :

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

비 클러스터형 인덱스 가 열을 포함 하도록 수정하는 경우 Oth:

DROP INDEX IX_Table1
ON dbo.Table1;
GO

CREATE INDEX IX_Table1
ON dbo.Table1 (Dat)
INCLUDE (Oth);        <---- This is the only change
GO

그런 다음 쿼리를 다시 실행하십시오.

SELECT *
FROM dbo.Table1
WHERE Dat = 'Test';

이제 SQL Server Dat = 'Test'IX_Table1인덱스에서 값이 포함 된 행 OthID열 값 (기본 키) 이 포함 된 행을 찾아야하므로 클러스터되지 않은 단일 인덱스 탐색을 볼 수 있습니다. 클러스터형 인덱스. 계획:

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


6

키 조회는 엔진이 페치하려고하는 모든 컬럼을 포함하지 않는 인덱스를 사용하도록 선택했기 때문에 발생합니다. 따라서 인덱스는 select 및 where 문의 열을 다루지 않습니다.

키 조회를 제거하려면 누락 된 열 (키 조회의 출력 목록에있는 열) = ProducerContactGuid, QuoteStatusID, PolicyTypeID 및 ProducerLocationID 또는 다른 방법으로 쿼리가 클러스터형 인덱스를 대신 사용하도록해야합니다.

테이블의 클러스터되지 않은 인덱스는 성능에 좋지 않을 수 있습니다. 업데이트, 삽입 또는 삭제를 실행할 때 SQL Server는 모든 인덱스를 업데이트해야합니다. 이 추가 작업은 성능에 부정적인 영향을 줄 수 있습니다.


또한 너무 많은 인덱스는 실행 계획의 컴파일을 혼란스럽게 할 수 있으며, 최적이 아닌 선택을 초래할 수도 있습니다.
Lopsided 2016

4

이 쿼리와 관련된 데이터의 양을 언급하는 것을 잊었습니다. 또한 왜 임시 테이블에 삽입합니까? 표시 해야하는 경우 insert 문을 실행하지 마십시오.

이 쿼리를 위해 tblQuotes27 개의 비 클러스터형 인덱스가 필요하지 않습니다. 클러스터형 인덱스 1 개와 비 클러스터형 인덱스 5 개 또는 비 클러스터형 인덱스 6 개가 필요합니다.

이 쿼리는 다음 열에 대한 인덱스를 원합니다.

QuoteStatusID
ProducerLocationID
ProducerContactGuid
EffectiveDate
LineGUID
OriginalQuoteGUID

또한 다음 코드를 발견했습니다.

DATEDIFF(D, @EffDateFrom, tblQuotes.EffectiveDate) >= 0 AND 
DATEDIFF(D, @EffDateTo, tblQuotes.EffectiveDate) <= 0

이다 NON Sargable는 인덱스를 활용할 수 없습니다 즉.

해당 코드 SARgable를 다음과 같이 변경 하려면 :

tblQuotes.EffectiveDate >= @EffDateFrom 
AND  tblQuotes.EffectiveDate <= @EffDateFrom

주요 질문에 대답하려면 "키를 찾는 이유":

당신은 점점 KEY Look up쿼리에 대한 언급이 칼럼의 일부가 포함 인덱스에 존재하지 않기 때문에.

당신이 구글과에 대해 공부할 수있는 Covering IndexInclude index.

내 예제에서 tblQuotes.QuoteStatusID가 비 클러스터형 인덱스라고 가정하면 DisplayStatus도 다룰 수 있습니다. Resultset에 DisplayStatus가 필요하기 때문입니다. 색인에없고 결과 집합에있는 열은 피하기 위해 포함 할 수 있습니다 KEY Look Up or Bookmark lookup. 다음은 인덱스를 다루는 예입니다.

create nonclustered index tblQuotes_QuoteStatusID 
on tblQuotes(QuoteStatusID)
include(DisplayStatus);

** 면책 조항 : ** 위의 내용은 제 예제입니다. DisplayStatus는 분석 후 다른 비 CI로 처리 될 수 있습니다.

마찬가지로 쿼리와 관련된 다른 테이블에서 인덱스를 만들고 인덱스를 포함해야합니다.

당신 Index SCAN도 계획을 세우고 있습니다.

테이블에 인덱스가 없거나 대량의 데이터가있는 경우 옵티마이 저가 인덱스 탐색을 수행하지 않고 스캔하기로 결정할 수 있습니다.

이로 인해 발생할 수도 있습니다 High cardinality. 잘못된 조인으로 인해 필요한 것보다 많은 수의 행을 가져옵니다. 이것도 정정 할 수 있습니다.

당사 사이트를 사용함과 동시에 당사의 쿠키 정책개인정보 보호정책을 읽고 이해하였음을 인정하는 것으로 간주합니다.
Licensed under cc by-sa 3.0 with attribution required.