Чому ця похідна таблиця покращує ефективність роботи?


18

У мене є запит, який бере параметр json як параметр. Json - це масив пар широти, довготи. Приклад введення може бути наступним.

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

Він називає TVF, який обчислює кількість точок зору навколо географічної точки на відстані 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))

план = 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)

план = 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)

Тож моє запитання, чому це має значення? Як я можу знати, коли я повинен обчислювати значення всередині похідної таблиці чи ні?


1
Під "вагою" ви маєте на увазі орієнтовну вартість%? Ця цифра практично безглузда, особливо коли ви приносите АДС, JSON, CLR за географією тощо.
Аарон Бертран

Я знаю, але, дивлячись на статистику IO, вони теж однакові. Обидва виконують 358306 логічних зчитувань на point_of_interestстолі, обидва сканують індекс 4602 рази, і обидва генерують робочий стіл та робочий файл. Оцінювач вважає, що ці плани однакові, але результативність говорить про інше.
Майкл Б

Мабуть, справжній процесор є проблемою тут, ймовірно, через те, що вказував Мартін, а не введення-виведення. На жаль, орієнтовні витрати базуються на комбінованому процесорі та вводу / виводу, і не завжди відображають те, що відбувається насправді. Якщо ви генеруєте фактичні плани за допомогою програми SentryOne Plan Explorer ( я працюю там, але інструмент безкоштовний без рядків ), то змініть фактичні витрати лише на процесор, ви можете отримати кращі показники того, де витрачався весь цей процесорний час.
Аарон Бертран

1
@MartinSmith Ще не в оператора, ні. Ми обробляємо їх на рівні заяви. В даний час ми все ще покладаємося на початкову реалізацію DMV, перш ніж ці додаткові показники були додані на нижньому рівні. І ми трохи зайняті роботою над чимось іншим, що незабаром побачите. :-)
Аарон Бертран

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. (Це псевдокод,
адаптуйте

Відповіді:


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 плану (анотація червоним кольором нижче від повільного плану та синім кольором для швидкого плану - час у мс)

введіть тут опис зображення

Потоковий агрегат має пройшов час на 6.209 секунд більше, ніж його найближчий дочір. І основну частину дитячого часу зайняв фільтр. Це відповідає додатковим оцінкам експресії.


До речі .... Взагалі це не впевнено, що основні вирази з мітками типу Expr1000обчислюються лише один раз і не переоцінюються, але чітко в цьому випадку від невідповідності строків виконання це відбувається тут.


Як осторонь, якщо я переключую запит на використання хреста, застосувати для створення географії, я також отримую швидкий план. cross apply(select geo=geography::Point( convert(float,json_value(value,'$[0]')) ,convert(float,json_value(value,'$[1]')) ,4326))f
Майкл Б

На жаль, але мені цікаво, чи існує простіший спосіб змусити його створити швидкий план.
Майкл Б

Вибачте за питання любителя, але який інструмент показаний на ваших зображеннях?
BlueRaja - Danny Pflughoeft

1
@ BlueRaja-DannyPflughoeft - це плани виконання, показані в студії управління (піктограми, які використовуються в SSMS, були оновлені в останніх версіях, якщо це було причиною питання)
Мартін Сміт
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.