다른 테이블에서 ORDER BY를 사용하여 TOP 1을 선택할 때 인덱싱 된 뷰를 설정하는 방법


11

다음 시나리오에서 인덱스 된 뷰를 설정하여 다음 쿼리가 두 개의 클러스터 된 인덱스 스캔없이 수행되도록 고심하고 있습니다. 이 쿼리에 대한 인덱스 뷰를 만들어서 사용할 때마다 내가 넣은 인덱스를 무시하는 것 같습니다.

    -- +++ THE QUERY THAT I WANT TO IMPROVE PERFORMANCE-WISE +++

    SELECT TOP 1 *
    FROM    dbo.TB_test1 t1
            INNER JOIN dbo.TB_test2 t2 ON t1.PK_ID1 = t2.FK_ID1
    ORDER BY t1.somethingelse1
           ,t2.somethingelse2;


    GO

테이블 설정은 다음과 같습니다.

  • 두 테이블
  • 위의 쿼리로 내부 조인으로 조인됩니다.
  • 그리고 상기 질의에 의해 제 1 테이블로부터의 열에이어서 제 2 테이블로부터의 열에 의해 정렬되고; TOP 1 만 선택
  • (아래 스크립트에는 문제를 재현하는 데 도움이되는 경우를 대비하여 테스트 데이터를 생성하는 행이 있습니다)

    -- +++ TABLE SETUP +++
    
    CREATE TABLE [dbo].[TB_test1]
        (
         [PK_ID1] [INT] IDENTITY(1, 1)  NOT NULL
        ,[something1] VARCHAR(40) NOT NULL
        ,[somethingelse1] BIGINT NOT NULL
            CONSTRAINT [PK_TB_test1] PRIMARY KEY CLUSTERED ( [PK_ID1] ASC )
        );
    
    GO
    
    create TABLE [dbo].[TB_test2]
        (
         [PK_ID2] [INT] IDENTITY(1, 1)  NOT NULL
        ,[FK_ID1] [INT] NOT NULL
        ,[something2] VARCHAR(40) NOT NULL
        ,[somethingelse2] BIGINT NOT NULL
            CONSTRAINT [PK_TB_test2] PRIMARY KEY CLUSTERED ( [PK_ID2] ASC )
        );
    
    GO
    
    ALTER TABLE [dbo].[TB_test2]  WITH CHECK ADD  CONSTRAINT [FK_TB_Test1] FOREIGN KEY([FK_ID1])
    REFERENCES [dbo].[TB_test1] ([PK_ID1])
    GO
    
    ALTER TABLE [dbo].[TB_test2] CHECK CONSTRAINT [FK_TB_Test1]
    
    GO
    
    
    -- +++ TABLE DATA GENERATION +++
    
    -- this might not be the quickest way, but it's only to set up test data
    
    INSERT INTO dbo.TB_test1
            ( something1, somethingelse1 )
    VALUES  ( CONVERT(VARCHAR(40), NEWID())  -- something1 - varchar(40)
              ,ISNULL(ABS(CHECKSUM(NewId())) % 92233720368547758078, 1)   -- somethingelse1 - bigint
              )
    
    GO 100000
    
    RAISERROR( 'Finished setting up dbo.TB_test1', 0, 1) WITH NOWAIT    
    
    GO    
    
    INSERT INTO dbo.TB_test2
            ( FK_ID1, something2, somethingelse2 )
    VALUES  ( ISNULL(ABS(CHECKSUM(NewId())) % ((SELECT MAX(PK_ID1) FROM dbo.TB_test1) - 1), 0) + 1 -- FK_ID1 - int
              ,CONVERT(VARCHAR(40), NEWID())  -- something2 - varchar(40)
              ,ISNULL(ABS(CHECKSUM(NewId())) % 92233720368547758078, 1)   -- somethingelse2 - bigint
              )
    
    GO 100000
    
    RAISERROR( 'Finished setting up dbo.TB_test2', 0, 1) WITH NOWAIT          
    
    GO

인덱싱 된 뷰는 다음과 같이 정의해야하며 결과 TOP 1 쿼리는 다음과 같습니다. 그러나 인덱스 된 뷰가없는 것보다이 쿼리의 성능이 향상 되려면 어떤 인덱스가 필요합니까?

    CREATE VIEW VI_test
    WITH SCHEMABINDING
    AS
        SELECT  t1.PK_ID1
               ,t1.something1
               ,t1.somethingelse1
               ,t2.PK_ID2
               ,t2.FK_ID1
               ,t2.something2
               ,t2.somethingelse2
        FROM    dbo.TB_test1 t1
                INNER JOIN dbo.TB_test2 t2 ON t1.PK_ID1 = t2.FK_ID1


    GO


    SELECT TOP 1 * FROM dbo.VI_test ORDER BY somethingelse1,somethingelse2


    GO

답변:


12

내가 넣은 색인을 무시하는 것 같습니다.

SQL Server Enterprise Edition (또는 시험판 및 개발자)을 사용하지 않는 한 WITH (NOEXPAND)뷰 참조 를 사용 하려면 뷰 참조를 사용해야합니다. 실제로 Enterprise 를 사용하는 경우에도 그 힌트를 사용해야 할 충분한 이유가 있습니다 .

힌트가 없으면 Enterprise Edition의 쿼리 최적화 프로그램이 구체화 된 뷰를 사용하거나 기본 테이블에 액세스하는 것 사이에서 비용 기반 선택을 할 수 있습니다. 뷰가 기본 테이블만큼 큰 경우이 계산은 기본 테이블을 선호 할 수 있습니다.

또 다른 관심 대상은 NOEXPAND힌트 가 없으면 뷰 참조가 최적화가 시작되기 전에 항상 기본 쿼리로 확장된다는 것입니다. 최적화가 진행됨에 따라 옵티마이 저는 이전 최적화 활동에 따라 확장 된 정의를 구체화 된 뷰와 다시 일치시킬 수도 있고 일치하지 않을 수도 있습니다. 이것은 간단한 쿼리의 경우에는 거의 해당되지 않지만 완전성에 대해서는 언급합니다.

따라서 NOEXPAND테이블 힌트를 사용하는 것이 기본 옵션이지만 기본 테이블 키와 뷰에서 순서를 지정하는 데 필요한 열을 구체화하는 것에 대해서만 생각할 수도 있습니다. 결합 된 키 열에 고유 한 클러스터형 인덱스를 생성 한 다음 순서 열에 별도의 비 클러스터형 인덱스를 생성하십시오.

이는 구체화 된 뷰의 크기를 줄이고 뷰를 기본 테이블과 동기화하기 위해 수행해야하는 자동 업데이트 수를 제한합니다. 그런 다음 뷰에서 필요한 순서대로 상위 1 개의 키를 가져 오기 위해 쿼리를 작성하고 (이상적으로로 NOEXPAND) 기본 테이블로 다시 조인하여 뷰의 키를 사용하여 나머지 열을 페치 할 수 있습니다.

또 다른 변형은 순서 열 및 테이블 키에서 뷰를 클러스터링 한 다음 키를 사용하여 기본 테이블에서 비보기 열을 수동으로 페치하는 쿼리를 작성하는 것입니다. 가장 적합한 옵션은 더 넓은 맥락에 달려 있습니다. 결정하는 좋은 방법은 실제 데이터와 작업량으로 테스트하는 것입니다.

기본 솔루션

CREATE VIEW VI_test
WITH SCHEMABINDING
AS
    SELECT
        t1.PK_ID1,
        t1.something1,
        t1.somethingelse1,
        t2.PK_ID2,
        t2.FK_ID1,
        t2.something2,
        t2.somethingelse2
    FROM dbo.TB_test1 t1
    INNER JOIN dbo.TB_test2 t2 
        ON t1.PK_ID1 = t2.FK_ID1;
GO
-- Brute force unique clustered index
CREATE UNIQUE CLUSTERED INDEX cuq 
ON dbo.VI_test 
    (somethingelse1, somethingelse2, PK_ID1, PK_ID2);
GO
SELECT TOP (1) * 
FROM dbo.VI_test WITH (NOEXPAND)
ORDER BY somethingelse1,somethingelse2;

실행 계획 :

무차별 대입 지수

비 클러스터형 인덱스 사용

-- Minimal unique clustered index
CREATE UNIQUE CLUSTERED INDEX cuq 
ON dbo.VI_test 
    (PK_ID1, PK_ID2)
WITH (DROP_EXISTING = ON);
GO
-- Nonclustered index for ordering
CREATE NONCLUSTERED INDEX ix 
ON dbo.VI_test (somethingelse1, somethingelse2);

실행 계획 :

주문을위한 비 클러스터형 인덱스

이 계획에는 조회가 있지만 단일 행을 가져 오는 데만 사용됩니다.

최소 인덱스보기

ALTER VIEW VI_test
WITH SCHEMABINDING
AS
    SELECT
        t1.PK_ID1,
        t2.PK_ID2,
        t1.somethingelse1,
        t2.somethingelse2
    FROM dbo.TB_test1 t1
    INNER JOIN dbo.TB_test2 t2 
        ON t1.PK_ID1 = t2.FK_ID1;
GO
-- Unique clustered index
CREATE UNIQUE CLUSTERED INDEX cuq 
ON dbo.VI_test 
    (somethingelse1, somethingelse2, PK_ID1, PK_ID2);

질문:

SELECT TOP (1)
    V.PK_ID1,
    TT1.something1,
    V.somethingelse1,
    V.PK_ID2,
    TT2.FK_ID1,
    TT2.something2,
    V.somethingelse2
FROM dbo.VI_test AS V WITH (NOEXPAND)
JOIN dbo.TB_test1 AS TT1 ON TT1.PK_ID1 = V.PK_ID1
JOIN dbo.TB_test2 AS TT2 ON TT2.PK_ID2 = V.PK_ID2
ORDER BY somethingelse1,somethingelse2;

실행 계획 :

최종 쿼리 계획

이는 검색되고있는 테이블 키 (보기 클러스터 된 인덱스에서 단일 행 페치)와 기본 테이블에서 두 개의 단일 행 조회로 나머지 열을 페치합니다.

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