검색을 기대하지만 스캔 받기


9

SELECT명령문 을 최적화해야 하지만 SQL Server는 항상 탐색 대신 인덱스 스캔을 수행합니다. 이것은 물론 저장 프로 시저에있는 쿼리입니다.

CREATE PROCEDURE dbo.something
  @Status INT = NULL,
  @IsUserGotAnActiveDirectoryUser BIT = NULL    
AS

    SELECT [IdNumber], [Code], [Status], [Sex], 
           [FirstName], [LastName], [Profession], 
           [BirthDate], [HireDate], [ActiveDirectoryUser]
    FROM Employee
    WHERE (@Status IS NULL OR [Status] = @Status)
    AND 
    (
      @IsUserGotAnActiveDirectoryUser IS NULL 
      OR 
      (
        @IsUserGotAnActiveDirectoryUser IS NOT NULL AND       
        (
          @IsUserGotAnActiveDirectoryUser = 1 AND ActiveDirectoryUser <> ''
        )
        OR
        (
          @IsUserGotAnActiveDirectoryUser = 0 AND ActiveDirectoryUser = ''
        )
      )
    )

그리고 이것은 색인입니다.

CREATE INDEX not_relevent ON dbo.Employee
(
    [Status] DESC,
    [ActiveDirectoryUser] ASC
)
INCLUDE (...all the other columns in the table...); 

계획:

계획 사진

SQL Server가 검사를 선택한 이유는 무엇입니까? 어떻게 고칠 수 있습니까?

열 정의 :

[Status] int NOT NULL
[ActiveDirectoryUser] VARCHAR(50) NOT NULL

상태 매개 변수는 다음과 같습니다.

NULL: all status,
1: Status= 1 (Active employees)
2: Status = 2 (Inactive employees)

IsUserGotAnActiveDirectoryUser는 다음이 될 수 있습니다.

NULL: All employees
0: ActiveDirectoryUser is empty for that employee
1: ActiveDirectoryUser  got a valid value (not null and not empty)

실제 실행 계획을 어딘가에 게시 할 수 있습니까 (그림은 아니지만 XML 형식의 .sqlplan 파일)? 내 생각에 당신은 절차를 변경했지만 실제로 진술 수준에서 새로운 편집을 얻지 못했습니다. 테이블 이름에 스키마 접두사를 추가하는 것과 같이 쿼리의 일부 텍스트를 변경 한 다음에 유효한 값을 전달할 수 @Status있습니까?
Aaron Bertrand

1
또한 인덱스 정의는 의문을 제기합니다. 왜 키가 중요한가 Status DESC? 에 대한 값은 몇 개 Status이며 무엇입니까 (숫자가 작은 경우) 각 값이 대략 동일하게 표시됩니까? 우리의 출력 표시SELECT TOP (20) [Status], c = COUNT(*) FROM dbo.Employee GROUP BY [Status] ORDER BY c DESC;
아론 버트 랜드

답변:


11

스캔이 빈 문자열을 검색했기 때문에 발생하지 않는다고 생각합니다 (이 경우 필터링 된 인덱스를 추가 할 수는 있지만 쿼리의 특정 변형에만 도움이됩니다). 매개 변수 스니핑의 대상이 될 가능성이 높으며이 쿼리에 제공 할 다양한 매개 변수 (및 매개 변수 값) 조합에 모두 최적화되지 않은 단일 계획입니다.

부엌 싱크대를 포함한 모든 것을 제공하기 위해 하나의 쿼리를 기대하기 때문에 이것을 "부엌 싱크대"절차라고 부릅니다 .

나는이 내 솔루션에 대한 비디오가 여기여기 뿐만 아니라 그것에 대해 블로그 게시물을 하지만, 기본적으로, 나는 그런 쿼리에 대해 가지고있는 최고의 경험을하는 것입니다

  • 명령문을 동적으로 빌드하십시오. 이를 통해 매개 변수가 제공되지 않은 열을 언급하는 절을 생략 할 수 있으며 값과 함께 전달 된 실제 매개 변수에 대해 정확하게 최적화 된 계획을 갖게 됩니다.
  • 사용OPTION (RECOMPILE) -특정 매개 변수 값이 잘못된 유형의 계획을 강요하지 못하도록합니다. 특히 데이터 왜곡, 잘못된 통계가 있거나 명령문을 처음 실행할 때 비정형 값을 사용하여 이후와 더 자주 다른 계획으로 이어질 때 유용합니다. 처형.
  • 서버 옵션 사용optimize for ad hoc workloads -한 번만 사용 된 쿼리 변형이 계획 캐시를 오염시키지 않도록합니다.

임시 워크로드에 최적화를 사용하십시오.

EXEC sys.sp_configure 'show advanced options', 1;
GO
RECONFIGURE WITH OVERRIDE;
GO
EXEC sys.sp_configure 'optimize for ad hoc workloads', 1;
GO
RECONFIGURE WITH OVERRIDE;
GO
EXEC sys.sp_configure 'show advanced options', 0;
GO
RECONFIGURE WITH OVERRIDE;

절차를 변경하십시오.

ALTER PROCEDURE dbo.Whatever
  @Status INT = NULL,
  @IsUserGotAnActiveDirectoryUser BIT = NULL
AS
BEGIN 
  SET NOCOUNT ON;
  DECLARE @sql NVARCHAR(MAX) = N'SELECT [IdNumber], [Code], [Status], 
     [Sex], [FirstName], [LastName], [Profession],
     [BirthDate], [HireDate], [ActiveDirectoryUser]
   FROM dbo.Employee -- please, ALWAYS schema prefix
   WHERE 1 = 1';

   IF @Status IS NOT NULL
     SET @sql += N' AND ([Status]=@Status)'

   IF @IsUserGotAnActiveDirectoryUser = 1
     SET @sql += N' AND ActiveDirectoryUser <> ''''';
   IF @IsUserGotAnActiveDirectoryUser = 0
     SET @sql += N' AND ActiveDirectoryUser = ''''';

   SET @sql += N' OPTION (RECOMPILE);';

   EXEC sys.sp_executesql @sql, N'@Status INT, @Status;
END
GO

모니터링 할 수있는 해당 쿼리 세트를 기반으로 한 워크로드가 있으면 실행을 분석하고 추가 또는 다른 인덱스를 통해 가장 많은 이점을 얻을 수있는 항목을 확인할 수 있습니다. 매개 변수가 가장 많이 제공됩니까? " "런타임이 가장 긴 개별 쿼리는 무엇입니까?" 우리는 당신의 코드를 기반으로 그 질문에 대답 할 수없는, 우리는 제안 할 수 있는 인덱스 만 지원를 시도하고 가능한 매개 변수의 모든 조합의 부분 집합에 대한 도움이 될 것입니다. 예를 들어@Status이 NULL이면 클러스터되지 않은 인덱스에 대한 탐색이 불가능합니다. 따라서 사용자가 상태를 신경 쓰지 않는 경우 다른 절을 제공하는 색인이 없으면 스캔을 받게됩니다 (그러나 현재 색인 논리를 고려할 때 이러한 색인은 유용하지 않습니다) -빈 문자열이거나 비어 있지 않은 문자열은 정확히 선택적이지 않습니다.

이 경우 가능한 Status값 세트 와 해당 값의 분포에 OPTION (RECOMPILE)따라 필요하지 않을 수 있습니다. 그러나 100 행을 생성하는 값과 수십만을 생성하는 값이있는 경우 (CPU 비용 으로도이 쿼리의 복잡성을 감안할 때 한계가 있음) 원하는 값을 가질 수 있습니다. 가능한 많은 경우에 구하십시오. 값의 범위가 충분히 유한 한 경우, 동적 SQL을 사용하여 까다로운 작업을 수행 할 수도 있습니다. 여기서 "에 대해이 선택적 값 @Status이 있습니다. 특정 값이 전달되면 쿼리 텍스트를 약간 변경하여 쿼리 텍스트를 약간 변경하십시오. 이는 다른 쿼리로 간주되며 해당 매개 변수 값에 최적화되어 있습니다. "


3
나는이 접근법을 여러 번 사용해 왔으며 옵티마이 저가 어쨌든 그렇게해야한다고 생각하는 방식으로 일하도록하는 환상적인 방법입니다. : 김 트립 여기에 유사한 솔루션에 대해 이야기 sqlskills.com/blogs/kimberly/high-performance-procedures을 그리고 그녀는 PASS 정말 작동 이유에 미친 내용이수록되어있는 전 몇 년에 한 세션의 비디오를 가지고있다. Bertrand 씨가 여기에 말한 것에 톤을 추가하지는 않습니다. 이것은 모든 사람이 툴 벨트에 보관해야하는 툴 중 하나입니다. 그것은 모든 잡기 쿼리에 대한 엄청난 고통을 절약 할 수 있습니다.
mskinner

3

면책 조항 :이 답변의 일부 내용은 DBA를 약화시킬 수 있습니다. 순수한 성능 관점에서 접근하고 있습니다. 항상 인덱스 스캔을받을 때 인덱스를 찾는 방법.

그 길을 벗어나면 여기로갑니다.

검색어는 "주방 싱크 쿼리"라고하며, 가능한 다양한 검색 조건을 충족시키는 단일 쿼리입니다. 사용자 @status가 값을 설정 하면 해당 상태를 필터링하려고합니다. 경우 @status이다 NULL, 등등의 모든 상태를 반환합니다.

이로 인해 색인 생성에 문제가 발생하지만 모든 검색 조건이 "같음"기준이기 때문에 Sargability와는 관련이 없습니다.

이것은 sargable입니다 :

WHERE [status]=@status

SQL Server가 인덱스에서 단일 값을 조회하는 대신 모든 행에 대해 평가해야하므로이 작업 은 불가능ISNULL([status], 0) 합니다.

WHERE ISNULL([status], 0)=@status

주방 싱크 문제를 더 간단한 형태로 다시 만들었습니다.

CREATE TABLE #work (
    A    int NOT NULL,
    B    int NOT NULL
);

CREATE UNIQUE INDEX #work_ix1 ON #work (A, B);

INSERT INTO #work (A, B)
VALUES (1,  1), (2,  1),
       (3,  1), (4,  1),
       (5,  2), (6,  2),
       (7,  2), (8,  3),
       (9,  3), (10, 3);

다음을 시도하면 A가 인덱스의 첫 번째 열이더라도 인덱스 스캔이 나타납니다.

DECLARE @a int=4, @b int=NULL;

SELECT *
FROM #work
WHERE (@a IS NULL OR @a=A) AND
      (@b IS NULL OR @b=B);

그러나 이것은 색인 탐색을 생성합니다.

DECLARE @a int=4, @b int=NULL;

SELECT *
FROM #work
WHERE @a=A AND
      @b IS NULL;

관리 가능한 양의 매개 변수 (귀하의 경우 2 개)를 사용하는 한 UNION기본적으로 검색 기준의 모든 순열과 같은 많은 검색 쿼리 일 수 있습니다. 세 가지 기준이 있다면 복잡해 보일 수 있으며, 네 가지 기준은 완전히 관리 할 수 ​​없습니다. 경고를 받았습니다.

DECLARE @a int=4, @b int=NULL;

SELECT *
FROM #work
WHERE @a=A AND
      @b IS NULL
UNION ALL
SELECT *
FROM #work
WHERE @a=A AND
      @b=B
UNION ALL
SELECT *
FROM #work
WHERE @a IS NULL AND
      @b=B
UNION ALL
SELECT *
FROM #work
WHERE @a IS NULL AND
      @b IS NULL;

이 네 가지 중 세 번째가 Index Seek를 사용하려면에 대한 두 번째 인덱스가 필요합니다 (B, A). 다음은 이러한 변경 사항을 통해 쿼리가 어떻게 보이는지 보여줍니다 (쿼리를 더 읽기 쉽게하기 위해 리팩토링 포함).

DECLARE @Status int = NULL,
        @IsUserGotAnActiveDirectoryUser bit = NULL;

SELECT [IdNumber], [Code], [Status], [Sex], [FirstName], [LastName],
       [Profession], [BirthDate], [HireDate], [ActiveDirectoryUser]
FROM Employee
WHERE [Status]=@Status AND
      @IsUserGotAnActiveDirectoryUser IS NULL

UNION ALL

SELECT [IdNumber], [Code], [Status], [Sex], [FirstName], [LastName],
       [Profession], [BirthDate], [HireDate], [ActiveDirectoryUser]
FROM Employee
WHERE [Status]=@Status AND
      @IsUserGotAnActiveDirectoryUser=1 AND ActiveDirectoryUser<>''

UNION ALL

SELECT [IdNumber], [Code], [Status], [Sex], [FirstName], [LastName],
       [Profession], [BirthDate], [HireDate], [ActiveDirectoryUser]
FROM Employee
WHERE [Status]=@Status AND
      @IsUserGotAnActiveDirectoryUser=0 AND (ActiveDirectoryUser IS NULL OR ActiveDirectoryUser='')

UNION ALL

SELECT [IdNumber], [Code], [Status], [Sex], [FirstName], [LastName],
       [Profession], [BirthDate], [HireDate], [ActiveDirectoryUser]
FROM Employee
WHERE @Status IS NULL AND
      @IsUserGotAnActiveDirectoryUser IS NULL

UNION ALL

SELECT [IdNumber], [Code], [Status], [Sex], [FirstName], [LastName],
       [Profession], [BirthDate], [HireDate], [ActiveDirectoryUser]
FROM Employee
WHERE @Status IS NULL AND
      @IsUserGotAnActiveDirectoryUser=1 AND ActiveDirectoryUser<>''

UNION ALL

SELECT [IdNumber], [Code], [Status], [Sex], [FirstName], [LastName],
       [Profession], [BirthDate], [HireDate], [ActiveDirectoryUser]
FROM Employee
WHERE @Status IS NULL AND
      @IsUserGotAnActiveDirectoryUser=0 AND (ActiveDirectoryUser IS NULL OR ActiveDirectoryUser='');

... 그리고 Employee두 개의 인덱스 열이 반대 인 추가 인덱스가 필요합니다 .

완전성을 위해, 나는 x=@x암시 적으로 그것이 결코 같지 않기 때문에 x불가능 하다는 것을 언급해야 합니다 . 이는 쿼리를 약간 단순화시킵니다.NULLNULLNULL

그렇습니다. Aaron Bertrand의 동적 SQL 응답은 대부분의 경우 (즉, 재 컴파일을 수행 할 수있을 때마다) 더 나은 선택입니다.


3

기본적인 질문은 "왜"인 것 같 으며 몇 년 전 TechEdAdam Machanic 이이 위대한 프레젠테이션에 대해 55 분 정도의 답을 찾은 것으로 생각 됩니다.

55 분에 5 분을 언급했지만 전체 프레젠테이션 시간이 가치가 있습니다. 쿼리에 대한 쿼리 계획을 살펴보면 검색에 대한 잔여 조건자가 있다는 것을 확신합니다. 기본적으로 SQL은 인덱스의 모든 부분을 "인식"할 수 없으며, 일부는 불평등과 다른 조건에 의해 숨겨져 있습니다. 결과는 술어에 기초한 수퍼 세트에 대한 인덱스 스캔입니다. 이 결과는 스풀링 된 다음 잔존 술어를 사용하여 다시 스캔됩니다.

스캔 연산자 (F4)의 속성을 확인하고 속성 목록에 "탐색자 찾기"와 "조건 자"가 모두 있는지 확인하십시오.

다른 사람들이 지적했듯이 쿼리는 그대로 인덱싱하기가 어렵습니다. 나는 최근에 비슷한 것들을 많이 연구 해 왔으며 각각 다른 해결책이 필요했습니다. :(


0

인덱스 검색이 인덱스 스캔보다 선호되는지 질문하기 전에 기본 테이블의 전체 행에 대해 리턴되는 행 수를 확인하는 것이 좋습니다. 예를 들어 쿼리가 백만 개의 행 중 10 개의 행을 반환 할 것으로 예상되면 인덱스 검색보다 인덱스 탐색이 선호됩니다. 그러나 쿼리에서 수천 행 (또는 그 이상)이 반환되는 경우 인덱스 검색이 반드시 선호되는 것은 아닙니다.

쿼리가 복잡하지 않으므로 실행 계획을 게시 할 수 있으면 더 나은 아이디어를 얻을 수 있습니다.


백만 개의 테이블에서 수천 행을 필터링하면 여전히 탐색을 원합니다. 여전히 전체 테이블을 스캔하는 것보다 성능이 크게 향상되었습니다.
Daniel Hutmacher

-6

이것은 단지 원본 형식입니다

DECLARE @Status INT = NULL,
        @IsUserGotAnActiveDirectoryUser BIT = NULL    

SELECT [IdNumber], [Code], [Status], [Sex], [FirstName], [LastName], [Profession],
       [BirthDate], [HireDate], [ActiveDirectoryUser]
FROM Employee
WHERE (@Status IS NULL OR [Status]=@Status)  
AND (            @IsUserGotAnActiveDirectoryUser IS NULL 
      OR (       @IsUserGotAnActiveDirectoryUser IS NOT NULL 
           AND (     @IsUserGotAnActiveDirectoryUser = 1 
                 AND ActiveDirectoryUser <> '') 
           OR  (     @IsUserGotAnActiveDirectoryUser = 0 
                 AND ActiveDirectoryUser =  '')
         )
    )

이것은 개정판입니다-100 % 확신 할 수는 없지만 (아마도) 시도해 보거나
아마도
ActiveDirectoryUser에서 중단 될 수있는 문제 일 것입니다.

  WHERE isnull(@Status, [Status]) = [Status]
    AND (      (     isnull(@IsUserGotAnActiveDirectoryUser, 1) = 1 
                 AND ActiveDirectoryUser <> '' ) 
           OR  (     isnull(@IsUserGotAnActiveDirectoryUser, 0) = 0 
                 AND ActiveDirectoryUser =  '' )
        )

3
이 답변이 OP의 질문을 어떻게 해결하는지는 분명하지 않습니다.
Erik

@Erik OP가 시도해 볼 수 있을까요? 두 수술실이 사라졌습니다. 이것이 쿼리 성능에 도움이되지 않는다는 것을 알고 있습니까?
paparazzo

@ ypercubeᵀᴹ IsUserGotAnActiveDirectoryUser IS NOT NULL이 제거되었습니다. 이 두 불필요한 OR을 제거하고 IsUserGotAnActiveDirectoryUser IS NULL을 제거하십시오. 이 쿼리가 OP보다 빠르게 실행되지 않습니까?
paparazzo

@ ypercubeᵀᴹ 많은 일을 할 수있었습니다. 나는 더 단순하지 않다. 둘 또는 사라. 또는 일반적으로 쿼리 계획에 좋지 않습니다. 나는 여기에 클럽이 있고 클럽의 일부가 아닙니다. 그러나 나는 살아있는 것을 위해 이것을하고 내가 알고있는 것을 게시했습니다. 내 답변은 다운 투표의 영향을받지 않습니다.
paparazzo
당사 사이트를 사용함과 동시에 당사의 쿠키 정책개인정보 보호정책을 읽고 이해하였음을 인정하는 것으로 간주합니다.
Licensed under cc by-sa 3.0 with attribution required.