Bu türetilmiş tablo neden performansı geliştiriyor?


18

Bir parametre olarak bir json dizesi alır bir sorgu var. Json enlem, boylam çiftler dizisidir. Örnek bir giriş aşağıdaki gibi olabilir.

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 mil mesafeden coğrafi bir noktadaki POI sayısını hesaplayan bir TVF çağırır.

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 sorgusunun amacı bu işlevi toplu olarak çağırmaktır. Bunu böyle adlandırırsam, performans sadece 4 puan için yaklaşık 10 saniye alarak çok kötüdür:

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

Bununla birlikte, coğrafyanın yapısını türetilmiş bir tablo içinde hareket ettirmek, performansın önemli ölçüde iyileşmesine neden olarak sorguyu yaklaşık 1 saniye içinde tamamlar.

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

Planlar neredeyse aynı görünüyor. Her ikisi de paralelliği kullanmaz ve her ikisi de mekansal indeksi kullanır. Yavaş plan üzerinde ipucu ile ortadan kaldırabileceğim ek bir tembel makara var option(no_performance_spool). Ancak sorgu performansı değişmez. Hala çok daha yavaş kalıyor.

Her ikisini de toplu olarak eklenen ipucuyla çalıştırmak her iki sorguyu da eşit olarak tartar.

Sql sunucusu sürümü = Microsoft SQL Server 2016 (SP1-CU7-GDR) (KB4057119) - 13.0.4466.4 (X64)

Benim sorum şu, bu neden önemli? Türetilmiş bir tablodaki değerleri ne zaman hesaplamam gerektiğini nasıl bilebilirim?


1
"Tartım" ile tahmini maliyet yüzdesi mi demek istersiniz? Bu sayı, özellikle UDF, JSON, CLR'yi coğrafya vb. İle getirdiğinizde neredeyse anlamsızdır
Aaron Bertrand

Biliyorum, ancak ES istatistiklerine bakıldığında da aynılar. Her ikisi de masada 358306 mantıksal okuma yapar point_of_interest, her ikisi de indeksi 4602 kez tarar ve her ikisi de bir çalışma masası ve çalışma dosyası oluşturur. Tahminci, bu planların aynı olduğuna inanıyor, ancak performans aksini söylüyor.
Michael B

Görünüşe göre gerçek CPU, I / O değil, Martin'in işaret ettiği şeyden kaynaklanıyor. Ne yazık ki tahmini maliyetler CPU ve G / Ç birleştirmesine dayanmaktadır ve gerçekte ne olduğunu her zaman yansıtmaz. SentryOne Plan Explorer'ı kullanarak gerçek planlar oluşturursanız ( orada çalışıyorum, ancak araç dizgisizdir ), daha sonra gerçek maliyetleri yalnızca CPU'ya değiştirin, tüm CPU zamanının nerede harcandığının daha iyi göstergelerini alabilirsiniz.
Aaron Bertrand

1
@MartinSmith Operatör başına değil, no. Bunları ifade düzeyinde ortaya koyuyoruz. Şu anda, bu ek metrikler daha düşük düzeyde eklenmeden önce DMV'nin ilk uygulamasına güveniyoruz. Ve yakında göreceğiniz başka bir şey üzerinde çalışmakla biraz meşgul olduk. :-)
Aaron Bertrand

1
Not: Düz çizgi mesafe hesaplaması yapmadan önce basit bir aritmetik kutu yaparak daha da fazla performans artışı elde edebilirsiniz. Yani, |LatLong.Lat - @geo.Lat| + |LatLong.Long - @geo.Long| < nsizden önceki değerin daha karmaşık olduğu yerler için filtreleyin sqrt((LatLong.Lat - @geo.Lat)^2 + (LatLong.Long - @geo.Long)^2). Ve daha da iyisi, önce üst ve alt sınırları hesaplayın, sonra LatLong.Lat > @geoLatLowerBound && LatLong.Lat < @geoLatUpperBound && LatLong.Long > @geoLongLowerBound && LatLong.Long < @geoLongUpperBound. (Bu sahte
koddur

Yanıtlar:


15

Sana performans farkı görüyoruz açıklıyor kısmi bir cevap verebilir - yine de bazı açık sorular bırakır olsa (örneğin kutu SQL Server daha optimum bir plan üretmek bir ara tablo ifade tanıtan olmadan bu projelerin bir sütun olarak ifade?)


Fark, hızlı planda JSON dizi öğelerini ayrıştırmak ve Coğrafya oluşturmak için gereken işin 4 kez ( openjsonişlevden yayılan her satır için bir kez ) yapılması, oysa yavaş planın 100.000 katından fazla yapılmasıdır .

Hızlı planda ...

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

Fonksiyonun Expr1000solundaki hesaplama skalerinde atanır openjson. Bu geo, türetilmiş tablo tanımınıza karşılık gelir .

resim açıklamasını buraya girin

Hızlı planda filtre ve akış toplam referansı Expr1000. Yavaş planda, altta yatan ifadenin tamamına atıfta bulunurlar.

Akış toplu özellikleri

resim açıklamasını buraya girin

Filtre, her uygulama bir ekspresyon değerlendirmesi gerektiren 116.995 kez yürütülür. Akış toplamı, toplama için içine akan 110.520 satıra sahiptir ve bu ifadeyi kullanarak üç ayrı toplama oluşturur. 110,520 * 3 + 116,995 = 448,555. Her bir değerlendirme 18 mikrosaniye sürse bile, bu sorgu için bir bütün olarak 8 saniye ek süre ekler.

Bunun etkisini plan XML'deki gerçek zaman istatistiklerinde görebilirsiniz (yavaş plandan aşağıda kırmızı ve açık plan için mavi olarak açıklanmıştır - süreler ms cinsindendir)

resim açıklamasını buraya girin

Akış toplamı, yakın zamanından 6.209 saniye daha uzun bir süreye sahiptir. Ve çocuk zamanının büyük kısmı filtre tarafından alındı. Bu, ekstra ifade değerlendirmelerine karşılık gelir.


Bu arada .... Genel olarak , etiketli temel ifadelerin Expr1000sadece bir kez hesaplandığından ve yeniden değerlendirilmediğinden emin değilim, ancak bu durumda yürütme zamanlaması tutarsızlığından açıkça burada olur.


Bir kenara, eğer coğrafyayı oluşturmak için çapraz uygulama kullanmak için sorguyu değiştirirsem, aynı zamanda hızlı planı alıyorum. cross apply(select geo=geography::Point( convert(float,json_value(value,'$[0]')) ,convert(float,json_value(value,'$[1]')) ,4326))f
Michael B

Talihsiz, ama hızlı planı oluşturmanın daha kolay bir yolu olup olmadığını merak ediyorum.
Michael B

Amatör soru için özür dilerim, ama resimlerinizde hangi araç gösteriliyor?
BlueRaja - Danny Pflughoeft

1
@ BlueRaja-DannyPflughoeft bunlar yönetim stüdyosunda gösterilen yürütme planlarıdır (bu sorunun nedeni SSMS'de kullanılan simgeler son sürümlerde güncellenmiştir)
Martin Smith
Sitemizi kullandığınızda şunları okuyup anladığınızı kabul etmiş olursunuz: Çerez Politikası ve Gizlilik Politikası.
Licensed under cc by-sa 3.0 with attribution required.