Saya punya permintaan yang menggunakan string json sebagai parameter. Json adalah array pasangan garis lintang dan bujur. Contoh input mungkin sebagai berikut.
declare @json nvarchar(max)= N'[[40.7592024,-73.9771259],[40.7126492,-74.0120867]
,[41.8662374,-87.6908788],[37.784873,-122.4056546]]';
Ini memanggil TVF yang menghitung jumlah POI di sekitar titik geografis, pada jarak 1,3,5,10 mil.
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
Maksud dari permintaan json adalah untuk memanggil fungsi ini secara massal. Jika saya menyebutnya seperti ini kinerjanya sangat buruk, membutuhkan waktu hampir 10 detik hanya untuk 4 poin:
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
Namun, memindahkan konstruksi geografi di dalam tabel turunan menyebabkan kinerja meningkat secara dramatis, menyelesaikan kueri dalam waktu sekitar 1 detik.
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
Rencana terlihat hampir identik. Tidak menggunakan paralelisme dan keduanya menggunakan indeks spasial. Ada gulungan malas tambahan pada rencana lambat yang bisa saya hilangkan dengan petunjuk option(no_performance_spool)
. Namun kinerja kueri tidak berubah. Masih jauh lebih lambat.
Menjalankan keduanya dengan petunjuk tambahan dalam satu batch akan menimbang kedua kueri secara merata.
Sql server versi = Microsoft SQL Server 2016 (SP1-CU7-GDR) (KB4057119) - 13.0.4466.4 (X64)
Jadi pertanyaan saya adalah mengapa ini penting? Bagaimana saya bisa tahu kapan saya harus menghitung nilai di dalam tabel turunan atau tidak?
sumber
point_of_interest
meja, keduanya memindai indeks 4602 kali, dan keduanya menghasilkan tabel kerja dan workfile. Estimator percaya bahwa rencana ini identik namun kinerja mengatakan sebaliknya.|LatLong.Lat - @geo.Lat| + |LatLong.Long - @geo.Long| < n
sebelum Anda lakukan semakin rumitsqrt((LatLong.Lat - @geo.Lat)^2 + (LatLong.Long - @geo.Long)^2)
. Dan yang lebih baik, hitung batas atas dan bawah terlebih dahuluLatLong.Lat > @geoLatLowerBound && LatLong.Lat < @geoLatUpperBound && LatLong.Long > @geoLongLowerBound && LatLong.Long < @geoLongUpperBound
. (Ini adalah kodesemu, adaptasi dengan tepat.)Jawaban:
Saya dapat memberi Anda jawaban parsial yang menjelaskan mengapa Anda melihat perbedaan kinerja - meskipun itu masih menyisakan beberapa pertanyaan terbuka (seperti bisakah SQL Server menghasilkan rencana yang lebih optimal tanpa memperkenalkan ekspresi tabel perantara yang memproyeksikan ekspresi sebagai kolom?)
Perbedaannya adalah bahwa dalam rencana cepat, pekerjaan yang diperlukan untuk mem-parsing elemen array JSON dan membuat Geografi dilakukan 4 kali (satu kali untuk setiap baris yang dipancarkan dari
openjson
fungsi) - sedangkan itu dilakukan lebih dari 100.000 kali dalam rencana lambat.Dalam rencana cepat ...
Ditugaskan ke
Expr1000
dalam scalar komputasi di sebelah kiriopenjson
fungsi. Ini sesuai dengangeo
definisi tabel turunan Anda.Dalam rencana cepat, filter dan streaming referensi agregat
Expr1000
. Dalam rencana yang lambat, mereka merujuk ekspresi penuh yang mendasarinya.Streaming properti agregat
Filter dijalankan 116.995 kali dengan setiap eksekusi membutuhkan evaluasi ekspresi. Agregat aliran memiliki 110.520 baris yang mengalir ke dalamnya untuk agregasi dan membuat tiga agregat terpisah menggunakan ungkapan ini.
110,520 * 3 + 116,995 = 448,555
. Bahkan jika setiap evaluasi individu membutuhkan 18 mikrodetik, ini menambahkan hingga 8 detik waktu tambahan untuk kueri secara keseluruhan.Anda dapat melihat efeknya dalam statistik waktu aktual dalam paket XML (dijelaskan dalam warna merah di bawah dari paket lambat dan biru untuk paket cepat - waktu ada dalam ms)
Agregat aliran memiliki waktu yang berlalu 6,209 detik lebih besar dari anak langsungnya. Dan sebagian besar waktu anak diambil oleh filter. Ini sesuai dengan evaluasi ekspresi ekstra.
Ngomong-ngomong .... Secara umum itu bukan hal yang pasti bahwa ekspresi yang mendasari dengan label seperti
Expr1000
hanya dihitung sekali dan tidak dievaluasi kembali tetapi jelas dalam hal ini dari perbedaan waktu eksekusi ini terjadi di sini.sumber
cross apply(select geo=geography::Point( convert(float,json_value(value,'$[0]')) ,convert(float,json_value(value,'$[1]')) ,4326))f