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

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)

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



답변

성능 차이가 발생하는 이유를 설명하는 부분 답변을 제공 할 수 있습니다.하지만 여전히 미심쩍은 질문이 남아 있습니다 (예 : 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 하지 않습니다.


답변