tempdb에 유출 될 가능성을 줄이기 위해 행 추정값을 개선하는 방법


11

tempdb 이벤트에 유출이 발생하면 (느린 쿼리 발생) 종종 특정 조인에 대해 행 추정이 중단되는 것을 알 수 있습니다. 병합 및 해시 조인으로 유출 이벤트가 발생하는 것을 보았으며 종종 런타임을 3 배에서 10 배로 증가시킵니다. 이 질문은 유출 사건의 가능성을 줄일 것이라는 가정 하에서 행 추정치를 개선하는 방법에 관한 것입니다.

실제 행 수 40k.

이 쿼리의 경우 계획에 잘못된 행 추정치 (11.3 행)가 표시됩니다.

select Value
  from Oav.ValueArray
 where ObjectId = (select convert(bigint, Value) NodeId
                     from Oav.ValueArray
                    where PropertyId = 3331  
                      and ObjectId = 3540233
                      and Sequence = 2)
   and PropertyId = 2840
option (recompile);

이 쿼리의 경우 계획에 양호한 행 예상치 (56k 행)가 표시됩니다.

declare @a bigint = (select convert(bigint, Value) NodeId
                       from Oav.ValueArray
                      where PropertyId = 3331
                        and ObjectId = 3540233
                        and Sequence = 2);

select Value
  from Oav.ValueArray
 where ObjectId = @a               
   and PropertyId = 2840
option (recompile);

첫 번째 경우에 대한 행 추정치를 향상시키기 위해 통계 또는 힌트를 추가 할 수 있습니까? 특정 필터 값 (속성 = 2840)으로 통계를 추가하려고 시도했지만 조합을 올바르게 얻을 수 없거나 컴파일 타임에 ObjectId를 알 수 없으므로 모든 ObjectId에 대한 평균을 선택했을 수 있으므로 무시됩니다.

프로브 쿼리를 먼저 수행 한 다음이를 사용하여 행 추정값을 결정하거나 맹목적으로 비행해야하는 모드가 있습니까?

이 특정 속성은 몇 가지 개체에 많은 값 (40k)이 있고 대부분의 경우 0입니다. 주어진 조인에 대해 예상되는 최대 행 수를 지정할 수있는 힌트에 만족합니다. 일부 매개 변수는 조인의 일부로 동적으로 결정되거나 뷰 내에 더 잘 배치 될 수 있기 때문에 일반적으로 귀찮은 문제입니다 (변수를 지원하지 않음).

tempdb 로의 유출 가능성을 최소화하기 위해 조정할 수있는 매개 변수가 있습니까 (예 : 쿼리 당 최소 메모리)? 강력한 계획은 추정에 영향을 미치지 않았습니다.

편집 2013.11.06 : 의견 및 추가 정보에 대한 답변 :

쿼리 계획 이미지는 다음과 같습니다. 경고는 convert ()의 카디널리티 / 검색 술어에 관한 것입니다.

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

@Aaron Bertrand의 의견에 따라 테스트로 convert ()를 바꾸려고했습니다.

create table Oav.SeekObject (
       LookupId bigint not null primary key,
       ObjectId bigint not null
);

insert into Oav.SeekObject (
   LookupId, ObjectId
) VALUES (
   1, 3540233
) 

select Value
  from Oav.ValueArray
 where ObjectId = (select ObjectId 
                     from Oav.SeekObject 
                    where LookupId = 1)
   and PropertyId = 2840
option (recompile);

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

이상하지만 성공적인 관심 지점으로, 조회를 단축시킬 수있었습니다.

select Value
  from Oav.ValueArray
 where ObjectId = (select ObjectId 
                     from Oav.ValueArray
                    where PropertyId = 2840
                      and ObjectId = 3540233
                      and Sequence = 2)
   and PropertyId = 2840
option (recompile);

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

이 두 가지 모두 올바른 키 조회를 나열하지만 첫 번째 키만 ObjectId의 "출력"을 나열합니다. 두 번째가 실제로 단락되었음을 나타냅니다.

누군가 행 예측을 돕기 위해 단일 행 프로브가 수행되는지 여부를 확인할 수 있습니까? 단일 행 PK 조회가 히스토그램에 대한 조회 정확도를 크게 향상시킬 수있는 경우 (특히 유출 가능성 또는 기록이있는 경우) 히스토그램 추정치로만 최적화를 제한하는 것은 잘못된 것 같습니다. 실제 쿼리에 이러한 하위 조인 중 10 개가있는 경우 이상적으로 병렬로 발생합니다.

참고로, sql_variant는 기본 유형 (SQL_VARIANT_PROPERTY = BaseType)을 필드 자체에 저장하기 때문에 "직접"변환 할 수있는 한 (예 : 문자열이 아닌 10 진수가 아니라 int 인 경우) convert ()가 거의 비용이 들지 않을 것으로 예상됩니다. int 또는 bigt to int). 컴파일 타임에는 알려지지 않았지만 사용자가 알 수 있으므로 sql_variants에 대한 "AssumeType (type, ...)"함수를 사용하면 더 투명하게 처리 할 수 ​​있습니다.


1
첫 번째 추측은 bigint로 변환하면 예상치가 사라지고 (쿼리 계획에 SQL Server 2012의 경고가 표시됨) 하위 쿼리는 0 또는 1 개의 행 이외의 다른 값을 반환 할 수 없다는 것입니다. 성공적인 쿼리. 당신이 가진 쿼리 계획을 보는 것이 흥미로울 것입니다. 아마도 XML 버전에 대한 링크 일 것입니다.
Mikael Eriksson 2016 년

2
서브 쿼리를 인라인하면 무엇을 얻을 수 있습니까? 개별적으로 꺼내는 것이 전체적으로 더 명확하고 더 나은 추정으로 이어지기 때문에 왜 그 방법을 사용하지 않습니까?
Aaron Bertrand

2
어떤 버전의 SQL Server입니까? 테이블에 대한 테이블 및 인덱스 DDL과 통계 블롭 (단일 및 다중 열)을 제공하여 문제 세부 정보를 볼 수 있습니까? declare @a bigint = 당신이 한 것처럼 쿼리를 나누는 것이 자연스러운 해결책 인 것 같습니다. 왜 받아 들일 수 없습니까?
Paul White 9

2
나는 당신의 디자인이 (매우 단순한) EAV 디자인이라고 생각합니다 CONVERT(). 이것은 대부분 효율적이지 않은 방법입니다. 이 특정 항목에서는 변환해야 할 값이 하나뿐이므로 문제는 아니지만 테이블에 어떤 색인이 있습니까? EAV 디자인은 일반적으로 적절한 인덱싱 (일반적으로 좁은 테이블에 많은 인덱스가 있음)만으로도 성능이 우수합니다.
ypercubeᵀᴹ

@Paul White는 분리하는 한 ...이 경우의 해결책으로 좋습니다. 그러나 좀 더 일반적인 / 복잡한 경우, 나는 대부분 병렬화와 가독성을 포기하고 싶지 않습니다. 이 중 10 개를 쿼리 내에 하위 쿼리 (일부는 더 복잡함)로 가지고 있지만 나머지 쿼리를 시작하기 전에 5 개만 "잘 익은"상태 여야한다고 가정 해보십시오.
crokusek

답변:


7

쿼리는 많은 고려가 필요하기 때문에 매우 단순 해 보이기 때문에 유출, tempdb 또는 힌트에 대해서는 언급하지 않습니다. 쿼리에 적합한 인덱스가 있으면 SQL-Server의 옵티마이 저가 그 일을 잘 수행 할 것이라고 생각합니다.

그리고 어떤 인덱스가 유용한 지 보여 주므로 두 개의 쿼리로 분할하는 것이 좋습니다. 첫 번째 부분 :

(select convert(bigint, Value) NodeId
 from Oav.ValueArray
 where PropertyId = 3331  
   and ObjectId = 3540233
   and Sequence = 2)

(PropertyId, ObjectId, Sequence)포함에 대한 색인이 필요 합니다 Value. 나는 그것을 UNIQUE안전하게 만들 것 입니다. 쿼리는 둘 이상의 행이 반환 된 경우 런타임 중에 오류를 발생 시키므로 고유 인덱스를 사용하여 이러한 상황이 발생하지 않도록 미리 확인하는 것이 좋습니다.

CREATE UNIQUE INDEX
    PropertyId_ObjectId_Sequence_UQ
  ON Oav.ValueArray
    (PropertyId, ObjectId, Sequence) INCLUDE (Value) ;

쿼리의 두 번째 부분 :

select Value
  from Oav.ValueArray
 where ObjectId = @a               
   and PropertyId = 2840

(PropertyId, ObjectId)포함에 대한 색인이 필요 합니다 Value.

CREATE INDEX
    PropertyId_ObjectId_IX
  ON Oav.ValueArray
    (PropertyId, ObjectId) INCLUDE (Value) ;

효율성이 향상되지 않거나 이러한 인덱스가 사용되지 않거나 행 추정치에 여전히 차이가있는 경우이 쿼리를 자세히 살펴 봐야합니다.

이 경우 EAV 디자인과 동일한 열에 다른 데이터 유형을 저장해야하는 변환이 가능한 원인이며 (@AAron Bertrand 및 @Paul White 설명과 같이) 쿼리를 두 부분으로 분할하는 솔루션은 자연스러운 것 같습니다 그리고 갈 길. 각 열에 다른 데이터 유형을 갖도록 다시 디자인하는 것이 다른 것일 수 있습니다.


테이블에는 인덱스가 포함되어있었습니다. 이 예제는 실제로 더 큰 쿼리를 제공하는 하위 조인이므로 tempdb 유출에 대한 모든 소란이 있습니다.
crokusek

5

통계 개선에 대한 명시 적 질문에 대한 부분 답변으로 ...

별도로 분리 된 경우에 대한 행 추정값은 여전히 ​​10X (4k 대 예상 40k)만큼 줄어 듭니다.

통계 히스토그램은 길고 (수직) 3.5M 행 테이블이고 특정 속성이 매우 희박하기 때문에 해당 속성에 대해 너무 얇게 분산되었을 수 있습니다.

스파 스 특성에 대한 추가 통계 (IX 통계와 중복 됨)를 작성하십시오.

create statistics [STAT_ValueArray_ObjPropValue_WhereProp2840] ON [Oav].[ValueArray](ObjectId, PropertyId, Value)
where PropertyId = 2840

원본:

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

convert ()가 제거 된 상태에서 (적절한) :

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

convert ()가 제거 된 상태 (짧은 회로) :

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

개체의> 99.9 %에 속성 2840이 정의되어 있지 않기 때문에 ~ 2X 정도는 여전히 꺼져 있습니다. 실제로이 테스트 사례의 경우이 속성은 3.5M 행 테이블의 200k 개별 개체 중 하나에 만 존재합니다. 정말 가까이 다가온 것이 놀랍습니다. 필터를 더 적은 ObjectId로 조정

create statistics [STAT_ValueArray_ObjPropValue_WhereProp2840] ON [Oav].[ValueArray](ObjectId, PropertyId, Value)
where PropertyId = 2840 and ObjectId >= 3540000 and ObjectId < 3541000

create statistics [STAT_ValueArray_ObjPropValue_WhereProp2840] ON [Oav].[ValueArray](ObjectId, PropertyId, Value)
where PropertyId = 2840 and ObjectId = 3540233;

흠, 변화 없음 ... 통계의 끝에 "전체 스캔으로"추가 (이전 두 가지가 작동하지 않은 이유 일 수 있음).

create statistics [STAT_ValueArray_ObjPropValue_WhereProp2840] ON [Oav].[ValueArray](ObjectId, PropertyId, Value)
where PropertyId = 2840 with full scan;

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

예 따라서 광범위하게 다루는 IX가있는 매우 수직 인 테이블에서 필터링 된 통계를 추가하면 크게 개선 된 것으로 보입니다 (특히 희소하지만 매우 다양한 키 조합의 경우).


다중 열 통계와 관련된 일부 문제에 대한 링크 입니다.
crokusek
당사 사이트를 사용함과 동시에 당사의 쿠키 정책개인정보 보호정책을 읽고 이해하였음을 인정하는 것으로 간주합니다.
Licensed under cc by-sa 3.0 with attribution required.