이 파생 테이블이 성능을 향상시키는 이유는 무엇입니까?


18

json 문자열을 매개 변수로 사용하는 쿼리가 있습니다. json은 위도, 경도 쌍의 배열입니다. 입력 예는 다음과 같습니다.

declare @json nvarchar(max)= N'[[40.7592024,-73.9771259],[40.7126492,-74.0120867]
,[41.8662374,-87.6908788],[37.784873,-122.4056546]]';

1,3,5,10 마일 거리의 지리적 지점 주변의 POI 수를 계산하는 TVF를 호출합니다.

create or alter function [dbo].[fn_poi_in_dist](@geo geography)
returns table
with schemabinding as
return 
select count_1  = sum(iif(LatLong.STDistance(@geo) <= 1609.344e * 1,1,0e))
      ,count_3  = sum(iif(LatLong.STDistance(@geo) <= 1609.344e * 3,1,0e))
      ,count_5  = sum(iif(LatLong.STDistance(@geo) <= 1609.344e * 5,1,0e))
      ,count_10 = count(*)
from dbo.point_of_interest
where LatLong.STDistance(@geo) <= 1609.344e * 10

json 쿼리의 목적은이 함수를 대량 호출하는 것입니다. 내가 이것을 이렇게 부르면 성능은 4 포인트에 거의 10 초가 걸리는 매우 열악합니다.

select row=[key]
      ,count_1
      ,count_3
      ,count_5
      ,count_10
from openjson(@json)
cross apply dbo.fn_poi_in_dist(
            geography::Point(
                convert(float,json_value(value,'$[0]'))
               ,convert(float,json_value(value,'$[1]'))
               ,4326))

계획 = https://www.brentozar.com/pastetheplan/?id=HJDCYd_o4

그러나 파생 테이블 내에서 지리 구성을 이동하면 성능이 크게 향상되어 약 1 초 안에 쿼리가 완료됩니다.

select row=[key]
      ,count_1
      ,count_3
      ,count_5
      ,count_10
from (
select [key]
      ,geo = geography::Point(
                convert(float,json_value(value,'$[0]'))
               ,convert(float,json_value(value,'$[1]'))
               ,4326)
from openjson(@json)
) a
cross apply dbo.fn_poi_in_dist(geo)

plan = https://www.brentozar.com/pastetheplan/?id=HkSS5_OoE

계획은 사실상 동일하게 보입니다. 병렬 처리를 사용하지 않으며 공간 인덱스를 사용합니다. 느린 계획에는 힌트로 제거 할 수있는 추가 게으른 스풀이 있습니다 option(no_performance_spool). 그러나 쿼리 성능은 변경되지 않습니다. 여전히 훨씬 느립니다.

배치에서 추가 된 힌트와 함께 둘 다 실행하면 두 쿼리의 가중치가 동일하게됩니다.

SQL Server 버전 = Microsoft SQL Server 2016 (SP1-CU7-GDR) (KB4057119)-13.0.4466.4 (X64)

내 질문은 왜 이것이 중요합니까? 파생 테이블 내에서 값을 계산해야하는 시점을 어떻게 알 수 있습니까?


1
"무게"란 예상 비용 %를 의미합니까? 이 숫자는 특히 지리 등을 통해 UDF, JSON, CLR을 가져올 때 사실상 의미가 없습니다.
Aaron Bertrand

나는 알고 있지만 IO 통계를 보면 그것들도 동일합니다. 둘 다 point_of_interest테이블에서 358306 논리적 읽기를 수행 하고 인덱스 4602 번을 스캔하며 작업 테이블과 작업 파일을 생성합니다. 견적 담당자는 이러한 계획은 동일하지만 성능은 그렇지 않다고 생각합니다.
Michael B

실제 CPU가 여기에서 문제가되는 것 같습니다 .I / O가 아니라 Martin이 지적한 것 같습니다. 불행하게도 예상 비용은 CPU와 I / O를 기반으로하며 실제 상황이 항상 반영되는 것은 아닙니다. SentryOne Plan Explorer를 사용하여 실제 계획을 생성하는 경우 (여기서 작업하지만 도구는 문자열없이 무료 임) 실제 비용을 CPU로만 변경하면 모든 CPU 시간이 소비 된 위치를 더 잘 알 수 있습니다.
Aaron Bertrand

1
@MartinSmith 아직 운영자 별로는 아닙니다. 우리는 진술 수준에서 그것들을 드러냅니다. 현재 추가 측정 항목이 하위 수준에 추가되기 전에 DMV의 초기 구현에 의존하고 있습니다. 그리고 우리는 당신이 곧 보게 될 다른 일을하기 위해 조금 바빴습니다. :-)
Aaron Bertrand

1
PS 직선 거리 계산을 수행하기 전에 간단한 산술 상자를 수행하면 성능이 훨씬 향상 될 수 있습니다. 즉 |LatLong.Lat - @geo.Lat| + |LatLong.Long - @geo.Long| < n,보다 복잡한 작업을 수행하기 전에 먼저 값을 필터링 하십시오 sqrt((LatLong.Lat - @geo.Lat)^2 + (LatLong.Long - @geo.Long)^2). 더 좋은 방법은 먼저 상한과 하한을 계산 한 다음 LatLong.Lat > @geoLatLowerBound && LatLong.Lat < @geoLatUpperBound && LatLong.Long > @geoLongLowerBound && LatLong.Long < @geoLongUpperBound입니다. (이것은 의사 코드이며 적절하게 조정됩니다.)
ErikE

답변:


15

성능 차이가 발생하는 이유를 설명하는 부분 답변을 제공 할 수 있습니다.하지만 여전히 미심쩍은 질문이 남아 있습니다 (예 : SQL Server가 식을 열로 투영하는 중간 테이블 식을 도입하지 않고도보다 최적의 계획을 생성 할 수 있습니까?)


차이점은 빠른 계획에서 JSON 배열 요소를 구문 분석하고 지리를 작성하는 데 필요한 작업은 4 번 ( openjson함수 에서 방출 된 각 행마다 한 번씩 ) 수행되지만 느린 계획에서는 100,000 이상 수행 된다는 것입니다.

빠른 계획에서 ...

geography::Point(
                convert(float,json_value(value,'$[0]'))
               ,convert(float,json_value(value,'$[1]'))
               ,4326)

함수 Expr1000의 왼쪽에있는 계산 스칼라에 할당됩니다 openjson. 이는 geo파생 테이블 정의에 해당합니다 .

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

빠른 계획에서 필터 및 스트림 집계 참조 Expr1000. 느린 계획에서 그들은 완전한 기본 표현을 참조합니다.

스트림 집계 속성

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

필터는 식 평가가 필요한 각 실행마다 116,995 번 실행됩니다. 스트림 집계에는 집계를 위해 110,520 개의 행이 흐르며이 식을 사용하여 세 개의 개별 집계를 만듭니다. 110,520 * 3 + 116,995 = 448,555. 각 개별 평가에 18 마이크로 초가 걸리더라도 쿼리 전체에 최대 8 초의 추가 시간이 추가됩니다.

계획 XML의 실제 시간 통계 에서이 효과를 볼 수 있습니다 (느린 계획에서 아래 빨간색으로 표시되고 빠른 계획의 경우 파란색으로 표시됩니다-시간은 ms입니다).

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

스트림 집계의 경과 시간은 직계 자식보다 6.209 초 더 깁니다. 그리고 자식 시간의 대부분은 필터에 의해 흡수되었습니다. 이는 추가 표현식 평가에 해당합니다.


그건 그렇고 .... 일반적으로 같은 레이블이있는 기본 표현식이 한 번만 계산되고 다시 평가되지는 않지만이 경우 실행 타이밍 불일치에서 분명히 발생 한다는 것은 확실Expr1000 하지 않습니다.


옆으로, 교차 적용을 사용하여 지리를 생성하도록 쿼리를 전환하면 빠른 계획도 얻습니다. cross apply(select geo=geography::Point( convert(float,json_value(value,'$[0]')) ,convert(float,json_value(value,'$[1]')) ,4326))f
Michael B

불행히도 빠른 계획을 세우는 쉬운 방법이 있는지 궁금합니다.
Michael B

아마추어 질문에 대해 죄송하지만 이미지에 어떤 도구가 표시됩니까?
BlueRaja-Danny Pflughoeft

1
@ BlueRaja-DannyPflughoeft 관리 스튜디오에 표시된 실행 계획입니다 (SSMS에 사용 된 아이콘이 문제의 원인 인 경우 최신 버전에서 업데이트되었습니다)
Martin Smith
당사 사이트를 사용함과 동시에 당사의 쿠키 정책개인정보 보호정책을 읽고 이해하였음을 인정하는 것으로 간주합니다.
Licensed under cc by-sa 3.0 with attribution required.