c# - बड़े डेटा के साथ SqlCommand Async विधियों का उपयोग करते हुए भयानक प्रदर्शन



.net sql-server (1)

महत्वपूर्ण लोड के बिना एक सिस्टम पर, एक एसिंक्स कॉल में थोड़ा बड़ा ओवरहेड होता है। हालांकि I / O ऑपरेशन अपने आप में अतुल्यकालिक है, भले ही ब्लॉकिंग थ्रेड-पूल टास्क स्विचिंग से तेज हो।

कितना उपरि? चलिए आपके टाइमिंग नंबर्स पर नजर डालते हैं। एक अवरुद्ध कॉल के लिए 30ms, एक अतुल्यकालिक कॉल के लिए 450ms। 32 kiB पैकेट आकार का मतलब है कि आपको लगभग पचास व्यक्ति I / O संचालन की आवश्यकता है। इसका मतलब है कि हमारे पास प्रत्येक पैकेट पर लगभग 8ms ओवरहेड है, जो विभिन्न पैकेट आकारों पर आपके माप के साथ बहुत अच्छी तरह से मेल खाता है। यह अतुल्यकालिक होने से बस ओवरहेड की तरह आवाज नहीं करता है, भले ही अतुल्यकालिक संस्करणों को सिंक्रोनस की तुलना में बहुत अधिक काम करने की आवश्यकता है। ऐसा लगता है कि तुल्यकालिक संस्करण है (सरलीकृत) 1 अनुरोध -> 50 प्रतिक्रियाएं, जबकि एसिंक्रोनस संस्करण 1 अनुरोध समाप्त होता है -> 1 प्रतिक्रिया -> 1 अनुरोध -> 1 प्रतिक्रिया -> ..., लागत का भुगतान अधिक से अधिक फिर।

गहराई तक जा रहे हैं। ExecuteReader सिर्फ ExecuteReader रूप में काम करता है। अगला ऑपरेशन GetFieldValue द्वारा Read जाता है - और वहां एक दिलचस्प बात होती है। यदि दोनों में से कोई भी असिंक है, तो पूरा ऑपरेशन धीमा है। एक बार जब आप चीजों को वास्तव में अतुल्यकालिक बनाने लगते हैं तो निश्चित रूप से बहुत कुछ अलग होता है - एक Read तेज़ होगा, और फिर GetFieldValueAsync धीमा हो जाएगा, या आप धीमे ReadAsync साथ शुरू कर सकते हैं, और फिर GetFieldValue और GetFieldValueAsync दोनों तेजी से होते हैं। स्ट्रीम से पहला अतुल्यकालिक पाठ धीमा है, और धीमापन पूरी तरह से पूरी पंक्ति के आकार पर निर्भर करता है। यदि मैं एक ही आकार की अधिक पंक्तियों को जोड़ता हूं, तो प्रत्येक पंक्ति को पढ़ने में उतना ही समय लगता है जैसे कि मेरे पास केवल एक पंक्ति है, इसलिए यह स्पष्ट है कि डेटा को अभी भी पंक्ति द्वारा पंक्तिबद्ध किया जा रहा है - यह केवल संपूर्ण पढ़ना पसंद करता है किसी भी अतुल्यकालिक पढ़ने शुरू करने के बाद एक बार पंक्ति। अगर मैं पहली पंक्ति को एसिंक्रोनस रूप से पढ़ता हूं, और दूसरी तुल्यकालिक रूप से - दूसरी पंक्ति को पढ़ा जा रहा है फिर से तेजी से होगा।

इसलिए हम देख सकते हैं कि समस्या एक व्यक्तिगत पंक्ति और / या स्तंभ का एक बड़ा आकार है। इससे कोई फर्क नहीं पड़ता कि आपके पास कुल कितना डेटा है - एक लाख छोटी पंक्तियों को असिंक्रोनस रूप से पढ़ना बस उतना ही तेज है जितना कि सिंक्रोनस। लेकिन बस एक ही फ़ील्ड जोड़ें जो एक पैकेट में फिट होने के लिए बहुत बड़ा है, और आप रहस्यमय तरीके से उस डेटा को अतुल्यकालिक रूप से पढ़ने पर लागत खर्च करते हैं - जैसे कि प्रत्येक पैकेट को एक अलग अनुरोध पैकेट की आवश्यकता होती है, और सर्वर सिर्फ सभी डेटा नहीं भेज सकता है एक बार। CommandBehavior.SequentialAccess का उपयोग करने से अपेक्षा के अनुरूप प्रदर्शन में सुधार होता है, लेकिन सिंक और एसिंक्स के बीच भारी अंतर अभी भी मौजूद है।

मुझे जो सबसे अच्छा प्रदर्शन मिला, वह पूरी तरह से सही तरीके से करने पर था। इसका मतलब है कि CommandBehavior.SequentialAccess का उपयोग करना, साथ ही साथ डेटा को स्पष्ट रूप से स्ट्रीमिंग करना:

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

इसके साथ, सिंक और एसिंक्स के बीच का अंतर मापना मुश्किल हो जाता है, और पैकेट के आकार को बदलना अब पहले से अधिक हास्यास्पद ओवरहेड को उकसाता नहीं है।

यदि आप किनारे के मामलों में अच्छा प्रदर्शन चाहते हैं, तो उपलब्ध सर्वोत्तम उपकरणों का उपयोग करना सुनिश्चित करें - इस मामले में, ExecuteScalar या GetFieldValue जैसे सहायकों पर भरोसा करने के बजाय बड़े कॉलम डेटा को स्ट्रीम करें।

मुझे एसक्यूएल कॉल का उपयोग करते समय मुख्य एसक्यूएल प्रदर्शन समस्याएं हैं। मैंने समस्या को प्रदर्शित करने के लिए एक छोटा सा मामला बनाया है।

मैंने SQL Server 2016 पर एक डेटाबेस बनाया है जो हमारे LAN में रहता है (इसलिए लोकलडीबी नहीं है)।

उस डेटाबेस में, मेरे पास 2 कॉलम के साथ एक टेबल WorkingCopy :

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]

उस तालिका में, मैंने एक एकल रिकॉर्ड ( id = 'PerfUnitTest' डाला है, Value 1.5mb स्ट्रिंग (बड़ा JSON डेटासेट का ज़िप) है।

अब, यदि मैं SSMS में क्वेरी निष्पादित करता हूं:

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

मुझे तुरंत परिणाम मिलता है, और मैं SQL Servre Profiler में देखता हूं कि निष्पादन का समय लगभग 20 मिलीसेकंड था। सभी सामान्य।

.NET (4.6) कोड से सादे 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;

इसके लिए निष्पादन का समय भी लगभग 20-30 मिलीसेकंड है।

लेकिन इसे बदलकर async कोड करने के लिए:

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

निष्पादन का समय अचानक 1800 एमएस है ! SQL Server Profiler में भी, मैं देखता हूं कि क्वेरी निष्पादन की अवधि एक सेकंड से अधिक है। हालांकि प्रोफाइलर द्वारा बताई गई निष्पादित क्वेरी बिल्कुल गैर-एस्किंक संस्करण के समान है।

लेकिन यह खराब हो जाता है। यदि मैं कनेक्शन स्ट्रिंग में पैकेट के आकार के साथ खेलता हूं, तो मुझे निम्नलिखित परिणाम मिलते हैं:

पैकेट का आकार 32768: [समय]: SqlValueStore में ExecuteScalarAsync -> बीता हुआ समय: 450 मि।

पैकेट का आकार 4096: [समय]: SqlValueStore में ExecuteScalarAsync -> बीता हुआ समय: 3667 ms

पैकेट का आकार 512: [समय]: SqlValueStore में ExecuteScalarAsync -> बीता हुआ समय: 30776 एमएस

30,000 एमएस !! यह गैर-ऐक्सिंक संस्करण की तुलना में 1000x से अधिक धीमा है। और SQL सर्वर प्रोफाइलर रिपोर्ट करता है कि क्वेरी निष्पादन 10 सेकंड से अधिक समय लगा। यह भी स्पष्ट नहीं है कि अन्य 20 सेकंड कहाँ गए हैं!

फिर मैंने सिंक संस्करण पर वापस स्विच किया और पैकेट के आकार के साथ भी खेला, और हालांकि इसने निष्पादन के समय को थोड़ा प्रभावित किया, यह async संस्करण के साथ नाटकीय रूप में कहीं नहीं था।

एक सिडेनोट के रूप में, यदि यह सिर्फ एक छोटी स्ट्रिंग (<100bytes) को मूल्य में रखता है, तो async क्वेरी निष्पादन सिंक संस्करण (परिणाम 1 या 2 एमएस में) के रूप में तेज़ है।

मैं वास्तव में इससे चकित हूं, खासकर जब से मैं अंतर्निहित SqlConnection का उपयोग कर रहा हूं, यहां तक ​​कि ORM भी नहीं। इसके अलावा जब आसपास खोज की, तो मुझे कुछ भी नहीं मिला जो इस व्यवहार की व्याख्या कर सके। कोई विचार?






async-await