Büyük veri içeren SqlCommand Async yöntemlerini kullanarak korkunç performans


95

Zaman uyumsuz çağrıları kullanırken önemli SQL performans sorunları yaşıyorum. Sorunu göstermek için küçük bir vaka oluşturdum.

LAN'ımızda bulunan (yani bir localDB değil) SQL Server 2016'da bir veritabanı oluşturdum.

Bu veritabanında WorkingCopy2 sütunlu bir tablom var:

Id (nvarchar(255, PK))
Value (nvarchar(max))

DDL

CREATE TABLE [dbo].[Workingcopy]
(
    [Id] [nvarchar](255) NOT NULL, 
    [Value] [nvarchar](max) NULL, 

    CONSTRAINT [PK_Workingcopy] 
        PRIMARY KEY CLUSTERED ([Id] ASC)
                    WITH (PAD_INDEX = OFF, STATISTICS_NORECOMPUTE = OFF, 
                          IGNORE_DUP_KEY = OFF, ALLOW_ROW_LOCKS = ON, 
                          ALLOW_PAGE_LOCKS = ON) ON [PRIMARY]
) ON [PRIMARY] TEXTIMAGE_ON [PRIMARY]

Bu tabloya tek bir kayıt idekledim ( = 'PerfUnitTest', Value1.5mb'lik bir dizedir (daha büyük bir JSON veri kümesinin zip'i)).

Şimdi, sorguyu SSMS'de yürütürsem:

SELECT [Value] 
FROM [Workingcopy] 
WHERE id = 'perfunittest'

Hemen sonucu alıyorum ve SQL Servre Profiler'da yürütme süresinin yaklaşık 20 milisaniye olduğunu görüyorum. Herşey normal.

Sorguyu .NET (4.6) kodundan bir düz kullanarak çalıştırırken SqlConnection:

// at this point, the connection is already open
var command = new SqlCommand($"SELECT Value FROM WorkingCopy WHERE Id = @Id", _connection);
command.Parameters.Add("@Id", SqlDbType.NVarChar, 255).Value = key;

string value = command.ExecuteScalar() as string;

Bunun uygulama süresi de yaklaşık 20-30 milisaniyedir.

Ancak onu eşzamansız koda değiştirirken:

string value = await command.ExecuteScalarAsync() as string;

Uygulama süresi aniden 1800 ms oldu ! Ayrıca SQL Server Profiler'da sorgu yürütme süresinin bir saniyeden fazla olduğunu görüyorum. Profil oluşturucu tarafından bildirilen yürütülen sorgu, Async olmayan sürümle tamamen aynı olsa da.

Ama daha da kötüleşiyor. Bağlantı dizesinde Paket Boyutu ile oynarsam, aşağıdaki sonuçları alıyorum:

Paket boyutu 32768: [TIMING]: SqlValueStore'da ExecuteScalarAsync -> geçen süre: 450 ms

Paket Boyutu 4096: [TIMING]: SqlValueStore'da ExecuteScalarAsync -> geçen süre: 3667 ms

Paket boyutu 512: [TIMING]: SqlValueStore'da ExecuteScalarAsync -> geçen süre: 30776 ms

30.000 ms !! Bu, eşzamansız olmayan sürümden 1000 kat daha yavaştır. Ve SQL Server Profiler, sorgu yürütmenin 10 saniyeden fazla sürdüğünü bildiriyor. Bu, diğer 20 saniyenin nereye gittiğini bile açıklamıyor!

Sonra senkron versiyona geri döndüm ve ayrıca Packet Size ile oynadım ve yürütme süresini biraz etkilemesine rağmen, hiçbir yerde asenkron versiyondaki kadar dramatik değildi.

Bir yan not olarak, değere yalnızca küçük bir dize (<100 bayt) koyarsa, eşzamansız sorgu yürütme, eşitleme sürümü kadar hızlıdır (sonuç 1 veya 2 ms'dir).

Bu beni gerçekten şaşkına çeviriyor, özellikle SqlConnectionbir ORM bile değil , yerleşik olanı kullandığım için. Ayrıca etrafta arama yaparken, bu davranışı açıklayabilecek hiçbir şey bulamadım. Herhangi bir fikir?


5
@hcd 1.5 MB ????? Ve neden paket boyutları küçüldükçe geri getirmenin yavaşladığını soruyorsunuz ? Özellikle BLOB'lar için yanlış sorgu kullandığınızda ?
Panagiotis Kanavos

3
@PanagiotisKanavos O sadece OP adına oynuyordu. Asıl soru, eşzamansızın aynı paket boyutuyla eşitlemeye kıyasla neden bu kadar yavaş olduğudur .
Fildor

2
Giriş ADO.NET Büyük-Değeri (maks) Verileri değiştirme CLOBs ve BLOB'ları alınırken doğru yolu. Bunun yerine tek bir büyük değeri, kullanım olarak okumaya çalışmak GetSqlCharsveya GetSqlBinarybir akış moda bunları almak için. Ayrıca bunları FILESTREAM verisi olarak saklamayı düşünün - bir tablonun veri sayfasında
1,5 MB

8
@PanagiotisKanavos Bu doğru değil. OP, eşitleme yazar: 20-30 ms ve her şey aynı 1800 ms ile zaman uyumsuz. Paket boyutunu değiştirmenin etkisi tamamen açıktır ve beklenmektedir.
Fildor

5
@hcd, sorunla ilgisiz göründüğü ve bazı yorumcular arasında kafa karışıklığına neden olduğu için paket boyutlarını değiştirme girişimlerinizle ilgili kısmı kaldırmışsınız gibi görünüyor.
Kuba Wyrostek

Yanıtlar:


141

Önemli bir yükün olmadığı bir sistemde, zaman uyumsuz bir çağrının biraz daha büyük bir ek yükü vardır. G / Ç işleminin kendisi ne olursa olsun zaman uyumsuz olsa da, engelleme, iş parçacığı havuzu görev anahtarlamasından daha hızlı olabilir.

Ne kadar ek yük? Zamanlama numaralarınıza bakalım. Engelleyen arama için 30 ms, senkronize olmayan arama için 450 ms. 32 kiB paket boyutu, yaklaşık elli ayrı I / O işlemine ihtiyacınız olduğu anlamına gelir. Bu, her pakette kabaca 8ms ek yükümüz olduğu anlamına gelir, bu da farklı paket boyutları üzerindeki ölçümlerinize oldukça iyi karşılık gelir. Zaman uyumsuz sürümlerin eşzamanlı sürümden çok daha fazla iş yapması gerekmesine rağmen, bu, eşzamansız olmaktan ek yük gibi gelmiyor. Eşzamanlı sürüm (basitleştirilmiş) 1 istek -> 50 yanıt gibi görünürken, eşzamansız sürüm 1 istek -> 1 yanıt -> 1 istek -> 1 yanıt -> ... maliyetini defalarca ödüyor tekrar.

Daha derine inmek. ExecuteReaderkadar iyi çalışır ExecuteReaderAsync. Bir sonraki işlemin Readardından a GetFieldValue- ve orada ilginç bir şey oluyor. İkisinden biri asenkron ise, tüm işlem yavaştır. Yani bir şey kesinlikle var çok şeylerin gerçekte asenkron yapmaya başlamak kez farklı olay - bir Readhızlı olacak ve daha sonra zaman uyumsuz GetFieldValueAsyncyavaş olacak, yoksa yavaş başlayabilir ReadAsyncve daha sonra hem GetFieldValueve GetFieldValueAsynchızlıdır. Akıştan ilk eşzamansız okuma yavaştır ve yavaşlık tamamen tüm satırın boyutuna bağlıdır. Ben aynı boyutta daha fazla satır eklerseniz sadece bir satır var sanki veri aşikardır böylece, her satır okuma, zaman aynı miktarda alır olduğunuhala satır satır yayınlanıyor - herhangi bir eşzamansız okumaya başladığınızda tüm satırı bir kerede okumayı tercih ediyor gibi görünüyor . İlk satırı eşzamansız olarak ve ikinci satırı eşzamanlı olarak okursam - okunan ikinci satır tekrar hızlı olacaktır.

Böylece sorunun tek bir satır ve / veya sütunun büyük bir boyutu olduğunu görebiliriz. Toplamda ne kadar veriye sahip olduğunuz önemli değil - bir milyon küçük satırı eşzamansız olarak okumak, eşzamanlı olarak olduğu kadar hızlıdır. Ancak tek bir pakete sığamayacak kadar büyük tek bir alan ekleyin ve bu verileri eşzamansız olarak okumaktan gizemli bir şekilde bir maliyete katlanıyorsunuz - sanki her paket ayrı bir istek paketine ihtiyaç duyuyormuş ve sunucu tüm verileri şu adrese gönderemiyormuş gibi bir Zamanlar. Kullanımı CommandBehavior.SequentialAccessbeklendiği gibi performansı iyileştirir, ancak senkronizasyon ile asenkron arasındaki büyük boşluk hala mevcuttur.

Aldığım en iyi performans, her şeyi doğru şekilde yapmaktı. Bu CommandBehavior.SequentialAccess, verileri açıkça kullanmak ve aynı zamanda akış olarak kullanmak anlamına gelir :

using (var reader = await cmd.ExecuteReaderAsync(CommandBehavior.SequentialAccess))
{
  while (await reader.ReadAsync())
  {
    var data = await reader.GetTextReader(0).ReadToEndAsync();
  }
}

Bununla, senkronizasyon ve asenkron arasındaki farkın ölçülmesi zorlaşır ve paket boyutunu değiştirmek artık eskisi gibi saçma ek yüklere neden olmaz.

Uç durumlarda iyi performans istiyorsanız, mevcut en iyi araçları kullandığınızdan emin olun - bu durumda, ExecuteScalarveya gibi yardımcılara güvenmek yerine büyük sütun verilerini yayınlayın GetFieldValue.


3
Mükemmel cevap. OP'nin senaryosunu yeniden oluşturdu. Bu 1.5m string OP için, senkronizasyon sürümü için 130ms, asenkron için 2200ms alıyorum. Yaklaşımınızla, 1,5 metrelik ip için ölçülen süre 60ms, fena değil.
Wiktor Zychla

4
Orada iyi araştırmalar yaptım, ayrıca DAL kodumuz için birkaç başka ayarlama tekniği öğrendim.
Adam Houldsworth

Ofise geri döndüm ve ExecuteScalarAsync yerine örneğimdeki kodu denedim, ancak yine de 512 bayt paket boyutuyla 30 saniye yürütme süresine sahibim :(
hcd

6
Aha, sonuçta işe yaradı :) Ama CommandBehavior.SequentialAccess'i bu satıra using (var reader = await command.ExecuteReaderAsync(CommandBehavior.SequentialAccess))
eklemem gerekiyor

@hcd Hatam, metinde vardı ama örnek kodda
yoktu
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.