실행 계획에서 비용 백분율에 너무 의존해서는 안됩니다. 이들은 항상 예상 비용 심지어 행 카운트 같은 것들에 대한 '실제'숫자 후 실행 계획에서. 예상 비용은 의도 한 목적을 위해 잘 작동하는 모델을 기반으로합니다. 최적화 프로그램이 동일한 쿼리에 대해 서로 다른 후보 실행 계획 중에서 선택할 수 있도록합니다. 비용 정보는 흥미롭고 고려해야 할 요소이지만 쿼리 튜닝을위한 기본 메트릭은 아닙니다. 실행 계획 정보를 해석하려면 제시된 데이터를 더 넓게 볼 필요가 있습니다.
ItemTran 클러스터형 인덱스 탐색 연산자
이 연산자는 실제로 두 가지 작업입니다. 먼저 인덱스 탐색 조작은 술어와 일치하는 모든 행을 찾은 product_code_v42 = 'M10BOLT'
다음 각 행에 나머지 술어가 bit_field_v41 & 4 = 0
적용됩니다. bit_field_v41
기본 유형 ( tinyint
또는 smallint
)에서 (으 ) 로의 암시 적 변환이 있습니다 integer
.
비트 AND 연산자 (&)는 두 피연산자가 동일한 유형이어야 하므로 변환이 발생합니다 . 상수 값 '4'의 암시 적 유형은 정수이고 데이터 유형 우선 순위 규칙 은 우선 순위가 낮은 bit_field_v41
필드 값이 변환 됨을 의미합니다 .
술어를 다음과 같이 작성하여 문제점을 쉽게 수정할 수 있습니다. bit_field_v41 & CONVERT(tinyint, 4) = 0
즉, 상수 값이 우선 순위가 낮고 열 값이 아닌 상수 폴딩 중에 변환됩니다. 이 변환 bit_field_v41
이 tinyint
없으면 전혀 발생하지 않습니다. 마찬가지로 is 인 CONVERT(smallint, 4)
경우 사용할 수 있습니다 . 즉,이 경우 변환은 성능 문제 가 아니지만 유형을 일치시키고 가능한 경우 암시 적 변환을 피하는 것이 좋습니다.bit_field_v41
smallint
이 탐색의 예상 비용의 주요 부분은 기본 테이블의 크기입니다. 클러스터 된 인덱스 키 자체는 상당히 좁지 만 각 행의 크기는 큽니다. 테이블에 대한 정의는 제공되지 않지만 뷰에 사용 된 열만 행 너비를 크게 증가시킵니다. 클러스터 된 인덱스의 모든 열을 포함하고 있으므로, 클러스터 인덱스 키 사이의 거리는의 폭 행 아닌 폭 인덱스 키 . 일부 열에 버전 접미사를 사용하면 실제 테이블에 이전 버전에 대한 열이 더 많이 있습니다.
탐색, 잔존 술어 및 출력 열을 보면이 연산자의 성능은 동등한 쿼리를 작성하여 분리하여 확인할 수 있습니다 ( 1 <> 2
자동 매개 변수화를 방지하기위한 트릭이며, 최적화 프로그램이 모순을 제거하며 쿼리 계획) :
SELECT
it.booking_no_v32,
it.QtyCheckedOut,
it.QtyReturned,
it.Trans_qty,
it.trans_type_v41
FROM dbo.tblItemTran AS it
WHERE
1 <> 2
AND it.product_code_v42 = 'M10BOLT'
AND it.bit_field_v41 & CONVERT(tinyint, 4) = 0;
미리 읽기는 테이블 (클러스터형 인덱스) 조각화의 영향을 받기 때문에 콜드 데이터 캐시를 사용한이 쿼리의 성능이 중요합니다. 이 테이블의 클러스터링 키는 조각화를 유발하므로이 인덱스를 정기적으로 유지 관리 (재구성 또는 재구성)하고 FILLFACTOR
인덱스 유지 관리 기간 사이에 새 행을위한 공간을 확보 하는 데 적절 해야합니다.
SQL Data Generator를 사용하여 생성 된 샘플 데이터를 사용하여 미리 읽기에 대한 조각화 효과를 테스트했습니다 . 질문의 쿼리 계획에 표시된 것과 동일한 테이블 행 수를 사용하여 고도로 조각난 클러스터형 인덱스는 SELECT * FROM view
15 초 후에 소요되었습니다 DBCC DROPCLEANBUFFERS
. 동일한 조건에서 ItemTrans 테이블에서 새로 재구성 된 클러스터형 인덱스를 사용하여 동일한 테스트를 3 초 만에 완료했습니다.
테이블 데이터가 일반적으로 캐시에 있으면 조각화 문제는 훨씬 덜 중요합니다. 그러나 조각화가 적더라도 테이블 행이 넓 으면 논리적 및 물리적 읽기 수가 예상보다 훨씬 많을 수 있습니다. CONVERT
모범 사례 위반을 제외하고 암시 적 변환 문제가 여기서 중요하지 않다는 기대를 확인 하기 위해 명시 적을 추가 및 제거하는 실험을 할 수도 있습니다 .
더 많은 것은 탐색 연산자를 떠나는 예상 행 수입니다. 최적화 시간 추정치는 165 행이지만 실행시 4,226이 생성되었습니다. 나중에이 시점으로 돌아갈 것이지만, 불일치의 주된 이유는 (비트 AND를 포함하는) 잔차 술어의 선택성이 옵티마이 저가 예측하기가 매우 어렵다는 것입니다.
필터 연산자
필자는 여기에 두 NOT IN
목록이 결합, 단순화 및 확장되는 방법을 설명하고 다음 해시 일치 토론에 대한 참조를 제공하기 위해 주로 필터 조건자를 표시합니다 . 탐색의 테스트 쿼리를 확장하여 해당 효과를 통합하고 필터 연산자가 성능에 미치는 영향을 확인할 수 있습니다.
SELECT
it.booking_no_v32,
it.trans_type_v41,
it.Trans_qty,
it.QtyReturned,
it.QtyCheckedOut
FROM dbo.tblItemTran AS it
WHERE
it.product_code_v42 = 'M10BOLT'
AND it.bit_field_v41 & CONVERT(tinyint, 4) = 0
AND
(
(
it.trans_type_v41 NOT IN (2, 3, 6, 7, 18, 19, 20, 21, 12, 13, 22)
AND it.trans_type_v41 NOT IN (6, 7)
)
OR
(
it.trans_type_v41 NOT IN (6, 7)
)
OR
(
it.trans_type_v41 IN (6, 7)
AND it.QtyCheckedOut = 0
)
OR
(
it.trans_type_v41 IN (6, 7)
AND it.QtyCheckedOut > 0
AND it.trans_qty - (it.QtyCheckedOut - it.QtyReturned) > 0
)
);
계획의 Compute Scalar 연산자는 다음 표현식을 정의합니다 (나중 연산자가 결과를 요구할 때까지 계산 자체가 연기 됨).
[Expr1016] = (trans_qty - (QtyCheckedOut - QtyReturned))
해시 일치 연산자
문자 데이터 유형에 대한 결합을 수행한다고해서이 연산자의 예상 비용이 높아지는 것은 아닙니다. SSMS 툴팁에는 해시 키 프로브 항목 만 표시되지만 중요한 세부 사항은 SSMS 속성 창에 있습니다.
해시 일치 연산자 booking_no_v32
는 ItemTran 테이블 의 열 값 (해시 키 빌드)을 사용하여 해시 테이블을 작성한 다음 booking_no
Bookings 테이블 의 열 (해시 키 프로브)을 사용하여 일치를 프로브합니다. SSMS 툴팁은 일반적으로 프로브 잔차를 표시하지만 텍스트가 툴팁에 비해 너무 길어서 생략되었습니다.
프로브 잔차는 색인이 더 일찍 검색된 후 나타나는 잔차와 유사합니다. 잔존 술어는 해시가 일치하는 모든 행에 대해 평가되어 행이 상위 연산자에 전달되어야하는지 여부를 판별합니다. 균형이 잘 잡힌 해시 테이블에서 해시 일치를 찾는 것은 매우 빠르지 만 일치하는 각 행에 복잡한 잔차 조건자를 적용하면 비교가 상당히 느립니다. 계획 탐색기의 해시 일치 도구 설명에 프로브 잔차 식을 포함한 세부 정보가 표시됩니다.
잔여 술어는 복잡하며 예약 테이블에서 열을 사용할 수있게되면서 예약 진행 상태 점검이 포함됩니다. 또한 툴팁은 인덱스 탐색에서 앞서 본 예상 행 수와 실제 행 수 사이의 동일한 불일치를 보여줍니다. 많은 필터링이 두 번 수행되는 것이 이상하게 보일 수 있지만 이것은 낙관적 인 최적화 프로그램 일뿐입니다. 행을 제거하기 위해 프로브 잔차에서 계획을 푸시 다운 할 수있는 필터 부분은 예상하지 않지만 (행 수 추정값은 필터 전후에 동일 함) 옵티마이 저는 그에 대해 잘못되었을 수 있음을 알고 있습니다. 행을 조기에 필터링 할 가능성 (해시 조인 비용 절감)은 추가 필터의 적은 비용으로 가치가 있습니다. 예약 테이블의 열에 대한 테스트가 포함되어 있기 때문에 전체 필터를 푸시 다운 할 수 없지만 대부분 필터를 푸시 다운 할 수 있습니다.
해시 테이블에 예약 된 메모리 양이 예상 행 수를 기반으로하기 때문에 과소 평가 된 행 수는 해시 일치 연산자의 문제입니다. 많은 수의 행으로 인해 런타임에 필요한 해시 테이블의 크기에 비해 메모리가 너무 작은 경우 해시 테이블은 재귀 적 으로 물리적 tempdb 스토리지에 유출 되어 성능이 매우 저하되는 경우가 많습니다. 최악의 경우, 실행 엔진은 재귀 적으로 해시 버킷을 쏟지 않고 매우 느리게 진행됩니다.구제 알고리즘. 해시 유출 (재귀 또는 구제)은 문제에 설명 된 성능 문제의 가장 큰 원인입니다 (문자 유형 조인 열 또는 암시 적 변환이 아님). 근본 원인은 잘못된 행 수 (카디널리티) 추정에 따라 서버가 쿼리에 대해 너무 적은 메모리를 예약하는 것입니다.
슬프게도 SQL Server 2012 이전에는 실행 계획에 해싱 작업이 메모리 할당을 초과했음을 나타내지 않습니다 (서버에 여유 메모리가 많은 경우에도 실행이 시작되기 전에 예약 된 후 동적으로 증가 할 수 없음). tempdb. 프로파일 러를 사용하여 해시 경고 이벤트 클래스 를 모니터링 할 수 있지만 경고를 특정 쿼리와 연관시키는 것은 어려울 수 있습니다.
문제 해결
세 가지 문제는 조각화, 해시 일치 연산자의 복잡한 프로브 잔차 및 인덱스 탐색시 추측으로 인한 잘못된 카디널리티 추정입니다.
권장 솔루션
조각화를 점검하고 필요한 경우 수정하여 색인이 수용 가능한 상태로 유지되도록 유지 보수를 스케줄하십시오. 카디널리티 예상을 수정하는 일반적인 방법은 통계를 제공하는 것입니다. 이 경우 옵티 마이저에는 조합 ( product_code_v42
, bitfield_v41 & 4 = 0
)에 대한 통계가 필요합니다 . 식에 대한 통계를 직접 만들 수 없으므로 먼저 비트 필드 식에 대한 계산 열을 만든 다음 수동 다중 열 통계를 만들어야합니다.
ALTER TABLE dbo.tblItemTran
ADD Bit3 AS bit_field_v41 & CONVERT(tinyint, 4);
CREATE STATISTICS [stats dbo.ItemTran (product_code_v42, Bit3)]
ON dbo.tblItemTran (product_code_v42, Bit3);
계산 된 열 텍스트 정의는 통계를 사용하기 위해 뷰 정의의 텍스트와 거의 일치해야하므로, 암시 적 변환을 제거하도록 뷰를 수정하는 동시에 텍스트 일치를 보장하기 위해주의를 기울여야합니다.
다중 열 통계는 훨씬 더 나은 추정 결과를 가져와 해시 일치 연산자가 재귀 적 유출 또는 구제 알고리즘을 사용할 가능성을 크게 줄여야합니다. 메타 데이터 전용 작업이며 표에 표시되지 않기 때문에 테이블에서 공간을 차지하지 않는 계산 열을 추가하면 PERSISTED
다중 열 통계가 첫 번째 해결책에서 가장 추측 할 수 있습니다.
쿼리 성능 문제를 해결할 때 경과 시간, CPU 사용량, 논리적 읽기, 물리적 읽기, 대기 유형 및 지속 시간 등과 같은 항목을 측정하는 것이 중요합니다. 위와 같이 의심되는 원인을 확인하기 위해 쿼리의 일부를 별도로 실행하는 것도 유용 할 수 있습니다.
최신 데이터보기가 중요하지 않은 일부 환경에서는 전체보기를 스냅 샷 테이블로 구체화하는 백그라운드 프로세스를 실행하는 것이 유용 할 수 있습니다. 이 테이블은 일반적인 기본 테이블 일 뿐이며 업데이트 성능에 영향을주지 않고 읽기 쿼리에 대해 색인을 생성 할 수 있습니다.
인덱싱보기
원래 뷰를 직접 색인화하려는 유혹을받지 마십시오. 읽기 성능은 놀랍도록 빠르지 만 (이 경우 뷰 인덱스에 대한 단일 검색) 기존 쿼리 계획의 모든 성능 문제는 뷰에서 참조되는 테이블 열을 수정하는 쿼리로 전송됩니다. 기본 테이블 행을 변경하는 쿼리는 실제로 매우 영향을받습니다.
부분 인덱스 뷰가있는 고급 솔루션
카디널리티 추정을 수정하고 필터 및 프로브 잔차를 제거하는이 특정 쿼리에 대한 부분 인덱스 뷰 솔루션이 있지만 데이터에 대한 몇 가지 가정 (주로 스키마에 대한 나의 추측)을 기반으로하며 특히 적절한 인덱싱 된 뷰 유지 관리 계획을 지원하는 인덱스 관심을 끌기 위해 아래 코드를 공유하므로 매우 신중한 분석 및 테스트 없이 코드를 구현할 것을 제안하지 않습니다 .
-- Indexed view to optimize the main view
CREATE VIEW dbo.V1
WITH SCHEMABINDING
AS
SELECT
it.ID,
it.product_code_v42,
it.trans_type_v41,
it.booking_no_v32,
it.Trans_qty,
it.QtyReturned,
it.QtyCheckedOut,
it.QtyReserved,
it.bit_field_v41,
it.prep_on,
it.From_locn,
it.Trans_to_locn,
it.PDate,
it.FirstDate,
it.PTimeH,
it.PTimeM,
it.RetnDate,
it.BookDate,
it.TimeBookedH,
it.TimeBookedM,
it.TimeBookedS,
it.del_time_hour,
it.del_time_min,
it.return_to_locn,
it.return_time_hour,
it.return_time_min,
it.AssignTo,
it.AssignType,
it.InRack
FROM dbo.tblItemTran AS it
JOIN dbo.tblBookings AS tb ON
tb.booking_no = it.booking_no_v32
WHERE
(
it.trans_type_v41 NOT IN (2, 3, 7, 18, 19, 20, 21, 12, 13, 22)
AND it.trans_type_v41 NOT IN (6, 7)
AND it.bit_field_v41 & CONVERT(tinyint, 4) = 0
)
OR
(
it.trans_type_v41 NOT IN (6, 7)
AND it.bit_field_v41 & CONVERT(tinyint, 4) = 0
AND tb.BookingProgressStatus = 1
)
OR
(
it.trans_type_v41 IN (6, 7)
AND it.bit_field_v41 & CONVERT(tinyint, 4) = 0
AND it.QtyCheckedOut = 0
)
OR
(
it.trans_type_v41 IN (6, 7)
AND it.bit_field_v41 & CONVERT(tinyint, 4) = 0
AND it.QtyCheckedOut > 0
AND it.trans_qty - (it.QtyCheckedOut - it.QtyReturned) > 0
);
GO
CREATE UNIQUE CLUSTERED INDEX cuq ON dbo.V1 (product_code_v42, ID);
GO
기존보기는 위의 색인보기를 사용하도록 조정되었습니다.
CREATE VIEW [dbo].[vwReallySlowView2]
AS
SELECT
I.booking_no_v32 AS bkno,
I.trans_type_v41 AS trantype,
B.Assigned_to_v61 AS Assignbk,
B.order_date AS dateo,
B.HourBooked AS HBooked,
B.MinBooked AS MBooked,
B.SecBooked AS SBooked,
I.prep_on AS Pon,
I.From_locn AS Flocn,
I.Trans_to_locn AS TTlocn,
CASE I.prep_on
WHEN 'Y' THEN I.PDate
ELSE I.FirstDate
END AS PrDate,
I.PTimeH AS PrTimeH,
I.PTimeM AS PrTimeM,
CASE
WHEN I.RetnDate < I.FirstDate
THEN I.FirstDate
ELSE I.RetnDate
END AS RDatev,
I.bit_field_v41 AS bitField,
I.FirstDate AS FDatev,
I.BookDate AS DBooked,
I.TimeBookedH AS TBookH,
I.TimeBookedM AS TBookM,
I.TimeBookedS AS TBookS,
I.del_time_hour AS dth,
I.del_time_min AS dtm,
I.return_to_locn AS rtlocn,
I.return_time_hour AS rth,
I.return_time_min AS rtm,
CASE
WHEN
I.Trans_type_v41 IN (6, 7)
AND I.Trans_qty < I.QtyCheckedOut
THEN 0
WHEN
I.Trans_type_v41 IN (6, 7)
AND I.Trans_qty >= I.QtyCheckedOut
THEN I.Trans_Qty - I.QtyCheckedOut
ELSE
I.trans_qty
END AS trqty,
CASE
WHEN I.Trans_type_v41 IN (6, 7)
THEN 0
ELSE I.QtyCheckedOut
END AS MyQtycheckedout,
CASE
WHEN I.Trans_type_v41 IN (6, 7)
THEN 0
ELSE I.QtyReturned
END AS retqty,
I.ID,
B.BookingProgressStatus AS bkProg,
I.product_code_v42,
I.return_to_locn,
I.AssignTo,
I.AssignType,
I.QtyReserved,
B.DeprepOn,
CASE B.DeprepOn
WHEN 1 THEN B.DeprepDateTime
ELSE I.RetnDate
END AS DeprepDateTime,
I.InRack
FROM dbo.V1 AS I WITH (NOEXPAND)
JOIN dbo.tblbookings AS B ON
B.booking_no = I.booking_no_v32
JOIN dbo.tblInvmas AS M ON
I.product_code_v42 = M.product_code;
쿼리 및 실행 계획 예 :
SELECT
vrsv.*
FROM dbo.vwReallySlowView2 AS vrsv
WHERE vrsv.product_code_v42 = 'M10BOLT';
새로운 계획에 해시 일치가 없습니다 잔류 술어 가없는, 더 복잡한 필터 , 여운 조건 인덱싱 된 뷰 추구에은과 카디널리티 예측은 정확히 정확합니다.
삽입 / 업데이트 / 삭제 계획이 어떻게 영향을 받는지에 대한 예로서, 이것은 ItemTrans 테이블에 대한 삽입 계획입니다.
강조 표시된 섹션은 새로 작성되었으며 인덱스 된 뷰 유지 보수에 필요합니다. 테이블 스풀은 인덱스 된 뷰 유지 보수를 위해 삽입 된 기본 테이블 행을 재생합니다. 각 행은 클러스터형 인덱스 탐색을 사용하여 bookings 테이블에 조인 된 다음 필터가 복합 WHERE
절 조건자를 적용 하여 행을 뷰에 추가해야하는지 확인합니다. 그렇다면 뷰의 클러스터 된 인덱스에 대한 삽입이 수행됩니다.
SELECT * FROM view
이전에 수행 된 동일한 테스트는 인덱스 된 뷰가있는 상태에서 150ms 내에 완료되었습니다.
마지막 사항 : 2008 R2 서버가 여전히 RTM에있는 것으로 보입니다. 성능 문제를 해결하지는 못하지만 2008 R2 용 서비스 팩 2는 2012 년 7 월부터 제공되었으며 서비스 팩을 최대한 최신 상태로 유지해야하는 여러 가지 이유가 있습니다.