Saya memiliki tampilan yang berjalan cepat (beberapa detik) hingga 41 catatan (misalnya, TOP 41
) tetapi membutuhkan beberapa menit untuk 44 atau lebih catatan, dengan hasil antara jika dijalankan dengan TOP 42
atau TOP 43
. Secara khusus, ini akan mengembalikan 39 catatan pertama dalam beberapa detik, kemudian berhenti selama hampir tiga menit sebelum mengembalikan catatan yang tersisa. Pola ini sama ketika meminta TOP 44
atau TOP 100
.
Tampilan ini awalnya berasal dari tampilan dasar, menambahkan hanya satu filter ke dasar, yang terakhir dalam kode di bawah ini. Tampaknya tidak ada perbedaan jika saya rantai tampilan anak dari pangkalan atau jika saya menulis tampilan anak dengan kode dari pangkalan di-lined. Tampilan dasar mengembalikan 100 catatan hanya dalam beberapa detik. Saya ingin berpikir bahwa saya bisa membuat pandangan anak berlari secepat basis, tidak 50 kali lebih lambat. Adakah yang melihat perilaku seperti ini? Adakah tebakan penyebab atau resolusi?
Perilaku ini telah konsisten selama beberapa jam terakhir karena saya telah menguji pertanyaan yang terlibat, meskipun jumlah baris yang dikembalikan sebelum hal-hal mulai melambat telah sedikit naik turun. Ini bukan hal baru; Saya melihatnya sekarang karena total waktu menjalankan sudah dapat diterima (<2 menit), tetapi saya telah melihat jeda ini dalam file log terkait selama berbulan-bulan, setidaknya.
Pemblokiran
Saya belum pernah melihat kueri diblokir, dan masalahnya ada bahkan ketika tidak ada aktivitas lain di database (seperti divalidasi oleh sp_WhoIsActive). Tampilan dasar mencakup NOLOCK
seluruh, untuk apa itu layak.
Pertanyaan
Berikut ini adalah versi cut-down dari tampilan anak, dengan tampilan dasar disatukan untuk kesederhanaan. Itu masih menunjukkan lompatan waktu berjalan di sekitar 40 catatan.
SELECT TOP 100 PERCENT
Map.SalesforceAccountID AS Id,
CAST(C.CustomerID AS NVARCHAR(255)) AS Name,
CASE WHEN C.StreetAddress = 'Unknown' THEN '' ELSE C.StreetAddress END AS BillingStreet,
CASE WHEN C.City = 'Unknown' THEN '' ELSE SUBSTRING(C.City, 1, 40) END AS BillingCity,
SUBSTRING(C.Region, 1, 20) AS BillingState,
CASE WHEN C.PostalCode = 'Unknown' THEN '' ELSE SUBSTRING(C.PostalCode, 1, 20) END AS BillingPostalCode,
CASE WHEN C.Country = 'Unknown' THEN '' ELSE SUBSTRING(C.Country, 1, 40) END AS BillingCountry,
CASE WHEN C.PhoneNumber = 'Unknown' THEN '' ELSE C.PhoneNumber END AS Phone,
CASE WHEN C.FaxNumber = 'Unknown' THEN '' ELSE C.FaxNumber END AS Fax,
TransC.WebsiteAddress AS Website,
C.AccessKey AS AccessKey__c,
CASE WHEN dbo.ValidateEMail(C.EMailAddress) = 1 THEN C.EMailAddress END, -- Removing this UDF does not speed things
TransC.EmailSubscriber
-- A couple dozen additional TransC fields
FROM
WarehouseCustomers AS C WITH (NOLOCK)
INNER JOIN TransactionalCustomers AS TransC WITH (NOLOCK) ON C.CustomerID = TransC.CustomerID
LEFT JOIN Salesforce.AccountsMap AS Map WITH (NOLOCK) ON C.CustomerID = Map.CustomerID
WHERE
C.DateMadeObsolete IS NULL
AND C.EmailAddress NOT LIKE '%@volusion.%'
AND C.AccessKey IN ('C', 'R')
AND C.CustomerID NOT IN (243566) -- Exclude specific test records
AND EXISTS (SELECT * FROM Orders AS O WHERE C.CustomerID = O.CustomerID AND O.OrderDate >= '2010-06-28') -- Only count customers who've placed a recent order
AND Map.SalesforceAccountID IS NULL -- Only count customers not already uploaded to Salesforce
-- Removing the ORDER BY clause does not speed things up
ORDER BY
C.CustomerID DESC
Yang Id IS NULL
membuang saringan sebagian catatan dikembalikan oleh BaseView
; tanpa TOP
klausa, mereka masing-masing mengembalikan 1.100 rekaman dan 267 ribu.
Statistik
Saat berlari TOP 40
:
SQL Server parse and compile time: CPU time = 234 ms, elapsed time = 247 ms.
SQL Server Execution Times: CPU time = 0 ms, elapsed time = 0 ms.
SQL Server Execution Times: CPU time = 0 ms, elapsed time = 0 ms.
(40 row(s) affected)
Table 'CustomersHistory'. Scan count 2, logical reads 39112, physical reads 0, read-ahead reads 0, lob logical reads 0, lob physical reads 0, lob read-ahead reads 0.
Table 'Orders'. Scan count 1, logical reads 752, physical reads 0, read-ahead reads 0, lob logical reads 0, lob physical reads 0, lob read-ahead reads 0.
Table 'AccountsMap'. Scan count 1, logical reads 458, physical reads 0, read-ahead reads 0, lob logical reads 0, lob physical reads 0, lob read-ahead reads 0.
SQL Server Execution Times: CPU time = 2199 ms, elapsed time = 7644 ms.
Saat berlari TOP 45
:
(45 row(s) affected)
Table 'CustomersHistory'. Scan count 2, logical reads 98268, physical reads 1, read-ahead reads 3, lob logical reads 0, lob physical reads 0, lob read-ahead reads 0.
Table 'Orders'. Scan count 1, logical reads 1788, physical reads 0, read-ahead reads 0, lob logical reads 0, lob physical reads 0, lob read-ahead reads 0.
Table 'AccountsMap'. Scan count 1, logical reads 2152, physical reads 0, read-ahead reads 0, lob logical reads 0, lob physical reads 0, lob read-ahead reads 0.
SQL Server Execution Times: CPU time = 41980 ms, elapsed time = 177231 ms.
Saya terkejut melihat jumlah lompatan membaca ~ 3x untuk perbedaan sederhana dalam output aktual.
Membandingkan rencana eksekusi, mereka sama dengan jumlah baris yang dikembalikan. Seperti halnya statistik di atas, jumlah baris aktual untuk langkah-langkah awal secara dramatis lebih tinggi dalam TOP 45
kueri, bukan hanya 12,5% lebih tinggi.
Secara garis besar, ini memindai indeks yang meliputi dari Pesanan, mencari catatan yang sesuai dari WarehouseCustomers; loop-menggabungkan ini ke TransactionalCustomers (permintaan jarak jauh, rencana pasti tidak diketahui); dan menggabungkan ini dengan pemindaian tabel AccountsMap. Kueri jarak jauh adalah 94% dari perkiraan biaya.
Catatan Lain-Lain
Sebelumnya, ketika saya mengeksekusi konten yang diperluas dari tampilan sebagai permintaan yang berdiri sendiri, itu berjalan cukup cepat: 13 detik untuk 100 catatan. Saya sekarang sedang menguji versi cut-down dari query, tanpa subqueries, dan query yang lebih sederhana ini membutuhkan waktu tiga menit untuk diminta mengembalikan lebih dari 40 baris, bahkan ketika dijalankan sebagai query yang berdiri sendiri.
Pandangan anak mencakup sejumlah besar pembacaan (~ 1M per sp_WhoIsActive), tetapi pada mesin ini (delapan core, 32 GB RAM, 95% kotak SQL khusus) yang biasanya tidak menjadi masalah.
Saya telah menjatuhkan dan menciptakan kembali kedua tampilan beberapa kali, tanpa perubahan.
Data tidak termasuk bidang TEKS atau BLOB. Satu bidang melibatkan UDF; menghapusnya tidak mencegah jeda.
Waktu serupa apakah menanyakan di server itu sendiri, atau di workstation saya 1.400 mil jauhnya, sehingga penundaan tampaknya melekat dalam permintaan itu sendiri daripada mengirim hasilnya ke klien.
Catatan Re: Solusinya
Perbaikannya ternyata sederhana: mengganti LEFT JOIN
to Map dengan NOT EXISTS
klausa. Ini hanya menyebabkan satu perbedaan kecil dalam rencana kueri, bergabung ke tabel TransactionCustomers (kueri jarak jauh) setelah bergabung ke tabel Peta daripada sebelumnya. Ini mungkin berarti bahwa itu hanya meminta catatan yang diperlukan dari server jarak jauh, yang akan mengurangi volume yang dikirimkan ~ 100 kali lipat.
Biasanya aku yang pertama bersorak untuk NOT EXISTS
; seringkali lebih cepat daripada LEFT JOIN...WHERE ID IS NULL
konstruksi, dan sedikit lebih kompak. Dalam kasus ini, ini canggung karena kueri masalah dibuat berdasarkan tampilan yang ada, dan sementara bidang yang diperlukan untuk anti-gabung diekspos oleh tampilan dasar, ia terlebih dahulu dilemparkan dari integer ke teks. Jadi untuk kinerja yang layak, saya harus menghentikan pola dua lapis dan alih-alih memiliki dua pandangan yang hampir identik, dengan yang kedua termasuk NOT EXISTS
klausa.
Terima kasih atas bantuan Anda dalam memecahkan masalah ini! Mungkin terlalu spesifik dengan keadaan saya untuk bisa membantu orang lain, tetapi mudah-mudahan tidak. Jika tidak ada yang lain, ini adalah contoh NOT EXISTS
menjadi lebih cepat daripada marjinal LEFT JOIN...WHERE ID IS NULL
. Tetapi pelajaran sebenarnya mungkin untuk memastikan bahwa pertanyaan jarak jauh digabungkan seefisien mungkin; rencana permintaan mengklaim bahwa itu mewakili 2% dari biaya, tetapi tidak selalu memperkirakan secara akurat.
sumber
Jawaban:
Beberapa hal untuk dicoba:
Periksa indeks Anda
Apakah semua
JOIN
bidang kunci diindeks? Jika Anda sering menggunakan tampilan ini, saya akan menambahkan indeks yang difilter untuk kriteria dalam tampilan. Contohnya...CREATE INDEX ix_CustomerId ON WarehouseCustomers(CustomerId, EmailAddress) WHERE DateMadeObsolete IS NULL AND AccessKey IN ('C', 'R') AND CustomerID NOT IN (243566)
Perbarui statistik
FULLSCAN
. Jika ada banyak baris, ada kemungkinan data telah berubah secara signifikan tanpa memicu perhitungan ulang otomatis.Bersihkan kueri
Buat
Map
JOIN
aNOT EXISTS
- Anda tidak memerlukan data dari tabel itu, karena Anda hanya ingin catatan yang tidak cocokHapus
ORDER BY
. Saya tahu komentar mengatakan bahwa itu tidak masalah tetapi saya merasa sangat sulit untuk percaya. Ini mungkin tidak penting untuk set hasil Anda yang lebih kecil karena halaman data sudah di-cache.sumber
LEFT JOIN...WHERE Id IS NULL
, saya mendapatkan jeda ini; sebagaiNOT EXISTS
klausa, run time adalah detik. Saya terkejut, tapi saya tidak bisa berdebat dengan hasilnya!Perbaikan 1 Hapus SubQuery untuk Pesanan dan ubah menjadi gabungan
Perbaikan 2 - Menyimpan catatan TransactionalCustomers dalam tabel Sementara Lokal
Pertanyaan Terakhir
Poin 3 - Saya berasumsi bahwa Anda memiliki indeks pada CustomerID, EmailAddress, OrderDate
sumber
EXISTS
biasanya lebih cepat daripadaJOIN
dalam keadaan ini, dan menghilangkan potensi dupes. Saya tidak berpikir itu akan menjadi perbaikan sama sekali.EXISTS
wajib. Juga, dalam pandangan saya tidak bisa men-cache data pelanggan yang digunakan kembali, meskipun saya telah bermain-main dengan gagasan dummy TVF tanpa parameter.