SQL Server 에서 매개 변수 화 된 SQL 쓰기 가 parameter sniff 를 만 나 계획 을 불합리 하 게 실행 하고 재 활용 하 는 빠 른 해결 방법 을 만 났 습 니 다.

parameter sniff 문 제 는 다른 매개 변수 로 생 성 된 실행 계획 을 다시 사용 하여 현재 매개 변 수 는 이 실행 계획 을 사용 하 는 것 이 최적화 되 지 않 은 현상 입 니 다.데 이 터 를 잘 아 는 학생 들 은 모두 알 아야 한다.parameter sniff 가 발생 하 는 가장 전형 적 인 문 제 는 매개 변수 화 된 SQL(또는 저장 과정 에서 매개 변수 화)기법 을 사용 하 는 것 이다.만약 에 데이터 분포 가 고 르 지 않 은 상황 에서 생 성 된 집행 계획 이 존재 하면 분포 데이터 가 비교적 많은 매개 변수 에 들 어 가 는 상황 에서정상 적 인 매개 변수 가 생 성 된 실행 계획 을 다시 사 용 했 습 니 다.이 캐 시 실행 계획 은 현재 매개 변수 에 적합 하지 않 습 니 다.
이러한 상황 은 실제 업무 에서 나타 나 는 빈도 가 비교적 높다.저장 과정 은 일반적으로 매개 변수 화 된 표기 법 을 사용 하기 때문이다.이때 분포 가 고 르 지 않 은 데이터 파 라 메 터 를 만 났 을 때 파 라 메 터 sniff 현상 이 나타 나 는데 이런 문 제 는 비교적 골 치 아프다.
구체 적 인 parameter sniff 가 발생 한 원인 에 대해 저 는 너무 많은 설명 을 하지 않 겠 습 니 다.이것 을 설명 하면 너무 낮 아 보 입 니 다.
저 는 간단 한 예 를 들 어 이 현상 을 모 의 한 다음 에 매개 변수 화 된 저장 과정 을 어떻게 썼 는 지,어떤 문제 가 존재 하 는 지,그리고 parameter sniff 문 제 를 어떻게 해결 하 는 지 설명 하 겠 습 니 다.
테스트 환경 만 들 기:

create table ParameterSniffProblem
(
id int identity(1,1),
CustomerId int,
OrderId int,
OrederStatus int,
CreateDate Datetime,
Remark varchar(200)
)
declare @i int = 0
while @i<500000
begin
INSERT INTO ParameterSniffProblem values (@i%10000,@i,RAND()*10,GETDATE()-RAND()*100,NEWID())
set @i=@i+1
end
--              ,            
INSERT INTO ParameterSniffProblem values (6666,RAND()*100000,1,GETDATE()-RAND()*100,NEWID())
GO 100000
--       
CREATE CLUSTERED INDEX IDX_CreateDate on ParameterSniffProblem(CreateDate
)
CREATE INDEX IDX_CustomerId ON ParameterSniffProblem(CustomerId)
매개 변수 화 저장 프로 세 스 의 쓰기:
저장 프로 세 스 를 작성 할 때 저 희 는 일반적으로 매개 변수 화 된 쓰기 방법 을 사용 하 는 것 을 권장 합 니 다.저장 프로 세 스 의 컴 파일 을 줄 이 고 실행 계획 캐 시 를 강화 하기 위해 서 입 니 다.
대략 이렇다

CREATE PROCEDURE [dbo].ParameterSniffTest 
( 
@p_CustomerId int,
@p_Status int,
@p_FromDate datetime,
@p_ToDate datetime
) 
AS 
BEGIN
SET NOCOUNT ON 
DECLARE
@Parm NVARCHAR(MAX),
@sqlcommand NVARCHAR(MAX) = N''
SET @sqlcommand = 'SELECT * FROM ParameterSniffProblem WHERE 1=1'
     IF(@p_CustomerId IS NOT NULL)
SET @sqlcommand = CONCAT(@sqlcommand,'AND CustomerId=@p_CustomerId ')
IF(@p_Status IS NOT NULL)
SET @sqlcommand = CONCAT(@sqlcommand,'AND OrederStatus=@p_Status ')
IF(@p_FromDate IS NOT NULL)
SET @sqlcommand = CONCAT(@sqlcommand,'AND CreateDate>=@p_FromDate ')
IF(@p_ToDate IS NOT NULL)
SET @sqlcommand = CONCAT(@sqlcommand,'AND CreateDate<=@p_ToDate ')
    SET @Parm= '@p_CustomerId int,
@p_Status   int,
@p_FromDate  datetime,
@p_ToDate   datetime '
    EXEC sp_executesql @sqlcommand,@Parm,
@p_CustomerId = @p_CustomerId,
@p_Status = @p_Status,
@p_FromDate = @p_FromDate,
@p_ToDate = @p_ToDate 
END
GO
파라미터 Sniff 문제:
이것 은 파 라 메 터 sniff 문제 가 잠재 되 어 있 습 니 다.
예 를 들 어 저 는 사용자 ID=100 의 주문 정 보 를 조회 하고 정상 적 인 분포 데 이 터 를 조회 합 니 다.저장 과정 이 처음으로 컴 파일 되 었 는데 이 실행 계획 은 전혀 문제 가 없습니다.

만약 에 제 가 파 라 메 터 를 바 꾸 어 사용자 6666 의 정 보 를 조회 하고 분포 와 불 균형 한 데 이 터 를 실행 합 니 다.그러나 상기 캐 시 실행 계획 을 다시 사용 하기 때문에 파 라 메 터 sniff 문제 가 발생 합 니 다.이 실행 계획 은 분명 합 리 적 이지 않 습 니 다.
IO 는 보지 않 고 일부러 만 든 예 입 니 다.

만약 에 제 가 실행 계획 캐 시 를 비우 고 상기 조 회 를 다시 실행 합 니 다.재 컴 파일 이 있 기 때문에 실행 계획 은 이 렇 지 않 습 니 다.CustomerID=6666 이라는 매개 변수 에 있어 서 전체 표 스 캔 대 가 는 더욱 작 아야 합 니 다.

이것 은 개발 에서 흔히 볼 수 있 는 문제 일 것 입 니 다.우리 에 게 매개 변수 화 SQL 은 서로 다른 매개 변수 에 대한 조 회 를 재 활용 하여 계획 을 실행 하 게 하 는 것 입 니 다.그러나 불행 하 게 도 데이터 분포 가 고 르 지 않 을 때 재 활용 계획 은 데이터 베이스 에 데 미 지 를 입 혔 습 니 다.예 를 들 어 정상 적 인 매개 변수 가 분포 가 많은 데 이 터 를 재 활용 한 실행 계획 이 라면 이름 은 색인 을 사용 할 수 있 습 니 다.결 과 는 시계 스 캔 이 고 결 과 는 더욱 심각 할 것 이다.
그렇다면 가능 한 한 실행 계획 을 재 활용 하고 싶 을 뿐만 아니 라 실행 계획 재 활용 으로 인해 parameter sniff 문제 가 발생 하지 않도록 하려 면 어떻게 해 야 합 니까?
우 리 는@p 가 문제 라 는 것 을 안다.customerId 에 게 parameter sniff 문제 가 발생 할 수 있 는@pcustomerId 는 매개 변수 화 를 하지 않 고 SQL 에 직접 맞 춥 니 다.만약@pcustomerId 가 변 하면 SQL 을 다시 컴 파일 합 니 다.즉,들 어 오 는@p 입 니 다.CustomerId 재 컴 파일
@p 라면customerId 는 변 하지 않 습 니 다.다른 매개 변 수 는 변화 가 있 습 니 다.예 를 들 어 이 시간 필드 의 변 화 는 매개 변수 화가 가 져 온 실행 계획 재 활용 의 장점 도 누 릴 수 있 습 니 다.즉,이렇게 처리 하 는 것 입 니 다@pcustomerId 이 매개 변 수 는@pcustomerId 가 SQL 문장 에 문자열 로 평평 하 게 모 으 면 즉석에서 조회 하 는 것 과 같 습 니 다.매개 변수 화 된 방식 으로 customerId 라 는 조회 조건 필드 에 값 을 부여 하지 않 습 니 다.

IF(@p_CustomerId IS NOT NULL)
SET @sqlcommand = CONCAT(@sqlcommand,'AND CustomerId= ',@p_CustomerId)
이렇게 저장 과정 을 수행 할 때,
@p 가 져 오기CustomerId=1 시,IDX 실행customerId 의 index seek

@p 가 져 오기customerId=6666 시 재 컴 파일,실행 계획 은 전체 표 스 캔 으로 위 에서 생 성 된 실행 계획 을 재 활용 하지 않 고 불합리한 실행 방식 이 효율 과 데이터 베이스 서버 자원 에 대한 소 모 를 초래 합 니 다.

이렇게 하면 parameter sniff 문제 가 가 져 오 는 영향 을 최대한 줄 일 수 있 습 니 다.캐 시 되 어 있 을 때@pcustomerId=1 의 실행 계획 을 실행 할 때 다시@pcustomerId=1,다른 조건 은 비교적 작은 변화 가 있 습 니 다.예 를 들 어 시간 필드 에 변경 이 있 으 면 캐 시 실행 계획 을 다시 사용 하여 컴 파일 에 미 치 는 영향 을 피 할 수 있 습 니 다.
결론:
이런 방식 은 parameter sniff 문 제 를 처리 하 는 것 입 니 다.물론 완벽 한 것 은 아 닙 니 다.분명 문제 가 있 을 것 입 니 다.저 는 당연히@pCustomerId 가 다 르 면 다시 컴 파일 해 야 합 니 다.
틀림없이@pcustomerId 매개 변수 값 이 다 르 기 때문에 재 컴 파일 의 기 회 를 피 할 수 없습니다.
그러나 불합리한 실행 계획 재 활용 으로 인 한 parameter sniff 문 제 는 없 을 것 이다.
parameter sniff 문제 가 발생 하면 대량의 조회 가 불합리한 실행 계획 을 사용 하면 전체 서버 에 심각 한 영향 을 미 칠 수 있다 는 것 을 알 아야 한다.예 를 들 어 대량의 IO 등 이 발생 할 수 있다.
첫 번 째@pCustomerId=1,
다시@pcustomerId=1.다른 조건 은 작은 변화 가 있 습 니 다.예 를 들 어 시간 필드 에 변경 이 있 으 면 캐 시 실행 계획 을 다시 사용 할 수 있 습 니 다.재 컴 파일 이 가 져 온 영향 을 피 할 수 있 습 니 다.물론 저 는 간단 한 예 일 뿐 실제 응용 에서 이것 보다 훨씬 복잡 합 니 다.
예 를 들 어 분 포 된 유 난 히 많은 데 이 터 는 두 가지 특징 이 있다.첫 번 째 분포 의 표 시 는 하나만 있 는 것 이 아니 라 두 번 째 분포 가 고 르 지 않 은 데 이 터 는 동태 적 인 것 이다.첫 번 째 분 포 는 A 라 는 부분의 데이터 가 대부분 을 차지 할 수도 있 고 두 번 째 분 포 는 B 데이터 가 절대 다 수 를 차지 할 수도 있다.
그래서 Plan Guide 로 parameter sniff 문 제 를 해결 하기 가 쉽 지 않 습 니 다.
이 방식 은 캐 시 실행 계획 을 어느 정도 재 활용 할 수 있 고 재 컴 파일 횟수 를 줄 일 수 있 습 니 다.
이 동시에 이런 방식 은 SQL 문자열 을 조합 하여 실행 하 는 즉석 조회 방식 에 비해 매개 변수 화가 가 져 온 다른 장점 도 이용 할 수 있다.예 를 들 어 SQL 주입 등 이다.
요약:
parameter sniff 문제 해결 방식 이 많 습 니 다.일일이 말 하지 않 겠 습 니 다.
가장 전형 적 인 것 은 강제 재 컴 파일 이다.
혹은 EXEC 를 사용 하여 맞 춤 형 문자열 을 실행 합 니 다.이 방식 은 adhoc 조회 에 속 합 니 다.
힌트 를 조회 하거나,
로 컬 변 수 를 사용 하거나,
      플랜 가이드 등 을 사용 하거나,
모든 방식 은 그의 한계 가 있다.적어도 지금까지 파 라 메 터 sniff 문 제 를 해결 하 는 완벽 한 방법 은 없다.
문제 에 부 딪 히 면 해결 방법 은 여러 가지 가 있 는데,최소한 의 대가 로 문 제 를 해결 하 는 것 이 왕도 다.

좋은 웹페이지 즐겨찾기