У меня есть запрос, который принимает строку JSON в качестве параметра. JSON - это массив пар широты и долготы. Пример ввода может быть следующим.
declare @json nvarchar(max)= N'[[40.7592024,-73.9771259],[40.7126492,-74.0120867]
,[41.8662374,-87.6908788],[37.784873,-122.4056546]]';
Он вызывает TVF, который вычисляет количество POI вокруг географической точки на расстоянии 1,3,5,10 мили.
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 является массовый вызов этой функции. Если я назову это так, производительность будет очень плохой и займет около 10 секунд всего за 4 балла:
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))
plan = 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-сервера = Microsoft SQL Server 2016 (SP1-CU7-GDR) (KB4057119) - 13.0.4466.4 (X64)
Итак, мой вопрос, почему это имеет значение? Как я могу узнать, когда мне следует вычислять значения внутри производной таблицы или нет?
источник
point_of_interest
таблицы, оба сканируют индекс 4602 раза, и оба генерируют рабочий стол и рабочий файл. Оценщик полагает, что эти планы идентичны, но показатели говорят об обратном.|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
. (Это псевдокод, адаптируйтесь соответствующим образом.)Ответы:
Я могу дать вам частичный ответ, который объясняет, почему вы видите разницу в производительности - хотя это все еще оставляет некоторые открытые вопросы (например, может ли SQL Server создать более оптимальный план без введения выражения промежуточной таблицы, которое проецирует выражение в виде столбца?)
Разница в том, что в быстром плане работа, необходимая для анализа элементов массива JSON и создания географии, выполняется 4 раза (по одному разу для каждой строки, получаемой из
openjson
функции), тогда как в медленном плане это выполняется более чем в 100 000 раз .В быстром плане ...
Назначается
Expr1000
в вычисляемом скаляре слева отopenjson
функции. Это соответствует определениюgeo
в вашей производной таблице.В быстром плане фильтр и ссылка на агрегат потока
Expr1000
. В медленном плане они ссылаются на полное базовое выражение.Свойства агрегатного потока
Фильтр выполняется 116 995 раз, при этом каждое выполнение требует вычисления выражения. Агрегат потока имеет 110 520 строк, входящих в него для агрегации, и создает три отдельных агрегата, используя это выражение.
110,520 * 3 + 116,995 = 448,555
, Даже если каждая отдельная оценка занимает 18 микросекунд, это добавляет до 8 секунд дополнительного времени для запроса в целом.Вы можете увидеть эффект этого в статистике фактического времени в XML-плане (ниже показан медленный план красным и синий для быстрого плана - время указано в мс)
Прошедшее время агрегата потока на 6,209 секунды больше, чем его непосредственный дочерний элемент. И большую часть детского времени занял фильтр. Это соответствует дополнительным оценкам выражений.
Между прочим .... В общем, это не совсем то, что базовые выражения с метками наподобие
Expr1000
рассчитываются только один раз и не переоцениваются, но в данном случае ясно из-за несоответствия времени выполнения, это происходит здесь.источник
cross apply(select geo=geography::Point( convert(float,json_value(value,'$[0]')) ,convert(float,json_value(value,'$[1]')) ,4326))f