2015-07-10 4 views
15

में बड़े (7MB) रिकॉर्ड डालने पर धीमी गति से इस प्रश्न का एक लंबा संस्करण है, और एक लघु संस्करण।सी #, एफई और LINQ: एसक्यूएल सर्वर

लघु संस्करण:

क्यों कर रहे हैं दोनों LINQ और EF एक दूरस्थ SQL सर्वर डेटाबेस में एक भी, बड़े (7 Mb) रिकॉर्ड डालने पर इतनी धीमी गति से?

और यहाँ लंबे संस्करण (समाधान बारे में कुछ जानकारी है, जो अन्य पाठकों के लिए उपयोगी हो सकता है के साथ):

निम्न उदाहरण कोड के सभी करता रन ठीक है, लेकिन के रूप में अपने उपयोगकर्ताओं को यूरोप में हैं और हमारे डेटा केंद्र अमेरिका में स्थित हैं, यह धीमा धीमा है। लेकिन अगर मैं अमेरिका में वर्चुअल पीसी पर एक ही कोड चलाता हूं, तो यह तुरंत चलता है। (और नहीं, दुख की बात है कि मेरी कंपनी घर में सभी डेटा रखना चाहता है, इसलिए मैं एज़ूर, अमेज़ॅन क्लाउड सर्विसेज इत्यादि का उपयोग नहीं कर सकता)

मेरे कुछ कॉर्पोरेट ऐप्स में एक्सेल से डेटा को पढ़ने/लिखने में शामिल है सर्वर, और अक्सर, हम Excel फ़ाइल तालिका में Excel फ़ाइल की कच्ची प्रतिलिपि सहेजना चाहेंगे।

यह बहुत करने के लिए सरल है, बस एक स्थानीय फ़ाइल से कच्चे डेटा में पढ़ने, और एक रिकार्ड में यह बचत।

private int SaveFileToSQLServer(string filename) 
{ 
    // Read in an Excel file, and store it in a SQL Server [External_File] record. 
    // 
    // Returns the ID of the [External_File] record which was added. 
    // 

    DateTime lastModifed = System.IO.File.GetLastWriteTime(filename); 
    byte[] fileData = File.ReadAllBytes(filename); 

    // Create a new SQL Server database record, containing our file's raw data 
    // (Note: the table has an IDENTITY Primary-Key, so will generate a ExtFile_ID for us.) 
    External_File newFile = new External_File() 
    { 
     ExtFile_Filename = System.IO.Path.GetFileName(filename), 
     ExtFile_Data = fileData, 
     ExtFile_Last_Modified = lastModifed, 
     Update_By = "mike", 
     Update_Time = DateTime.UtcNow 
    }; 
    dc.External_Files.InsertOnSubmit(newFile); 
    dc.SubmitChanges(); 

    return newFile.ExtFile_ID; 
} 

हाँ, कोई आश्चर्य नहीं है, और यह ठीक काम करता है।

लेकिन, मैं क्या देखा है कि बड़े Excel फ़ाइलों (7-8Mb) के लिए, इस कोड को एक (बड़े!) रिकॉर्ड को चलाने के लिए 40-50 सेकंड ले जाएगा डालने के लिए है। मैंने इसे पृष्ठभूमि धागे में रखा, और यह सब ठीक काम किया, लेकिन, ज़ाहिर है, अगर उपयोगकर्ता मेरे आवेदन को छोड़ देता है, तो इस प्रक्रिया को मार दिया जाएगा, जिससे समस्याएं पैदा होंगी। एसक्यूएल सर्वर मशीन

  • एक संग्रहीत प्रक्रिया कहा जाता है कच्चे डेटा को पढ़ने के लिए पर एक साझा निर्देशिका में

    • प्रतिलिपि फ़ाइल:

      एक परीक्षण के रूप में, मैं यह करने के लिए कोड के साथ इस समारोह को बदलने के लिए करने की कोशिश की (ब्लॉब) एक ही तालिका में

    इस विधि का उपयोग करके, पूरी प्रक्रिया में केवल 3-4 सेकंड लगेंगे।

    CREATE PROCEDURE [dbo].[UploadFileToDatabase] 
        @LocalFilename nvarchar(400) 
    AS 
    BEGIN 
        -- By far, the quickest way to do this is to copy the file onto the SQL Server machine, then call this stored 
        -- procedure to read the raw data into a [External_File] record, and link it to the Pricing Account record. 
        -- 
        --  EXEC [dbo].[UploadPricingToolFile] 'D:\ImportData\SomeExcelFile.xlsm' 
        -- 
        -- Returns: -1 if something went wrong (eg file didn't exist) or the ID of our new [External_File] record 
        -- 
        -- Note that the INSERT will go wrong, if the user doesn't have "bulkadmin" rights. 
        --  "You do not have permission to use the bulk load statement." 
        -- EXEC master..sp_addsrvrolemember @loginame = N'GPP_SRV', @rolename = N'bulkadmin' 
        -- 
        SET NOCOUNT ON; 
    
        DECLARE 
         @filename nvarchar(300),  -- eg "SomeFilename.xlsx" (without the path) 
         @SQL nvarchar(2000), 
         @New_ExtFile_ID int 
    
        -- Extract (just) the filename from our Path+Filename parameter 
        SET @filename = RIGHT(@LocalFilename,charindex('\',reverse(@LocalFilename))-1) 
    
        SET @SQL = 'INSERT INTO [External_File] ([ExtFile_Filename], [ExtFile_Data]) ' 
        SET @SQL = @SQL + 'SELECT ''' + @Filename + ''', * 
        SET @SQL = @SQL + ' FROM OPENROWSET(BULK ''' + @LocalFilename +''', SINGLE_BLOB) rs' 
    
        PRINT convert(nvarchar, GetDate(), 108) + ' Running: ' + @SQL 
        BEGIN TRY 
         EXEC (@SQL) 
         SELECT @New_ExtFile_ID = @@IDENTITY 
        END TRY 
        BEGIN CATCH 
         PRINT convert(nvarchar, GetDate(), 108) + ' An exception occurred.' 
         SELECT -1 
         RETURN 
        END CATCH 
    
        PRINT convert(nvarchar, GetDate(), 108) + ' Finished.' 
    
        -- Return the ID of our new [External_File] record 
        SELECT @New_ExtFile_ID 
    END 
    

    कुंजी करने के लिए:

    आप रुचि रखते हैं, यहाँ संग्रहित प्रक्रिया मैं एक फ़ाइल एक डेटाबेस रिकॉर्ड में (जो SQL सर्वर मशीन पर ही एक फ़ोल्डर में संग्रहीत किया जाना चाहिए) अपलोड करने के लिए प्रयोग किया जाता है

    INSERT INTO [External_File] ([ExtFile_Filename], [ExtFile_Data]) 
    SELECT 'SomeFilename.xlsm', * FROM OPENROWSET(BULK N'D:\ImportData\SomeExcelFile.xlsm', SINGLE_BLOB) rs 
    

    .. और, के रूप में दोनों डेटाबेस और फ़ाइल अपलोड होने में दोनों एक ही मशीन पर कर रहे हैं, यह लगभग तुरंत चलाता है: इस कोड है कि वह इस तरह का एसक्यूएल आदेश बनाता है।

    जैसा कि मैंने कहा, कुल मिलाकर, फ़ाइल को SQL सर्वर मशीन पर किसी फ़ोल्डर में कॉपी करने के लिए 3-4 सेकंड लग गए, और LINQ के साथ C# कोड का उपयोग करके 40-50 सेकेंड की तुलना में इस संग्रहीत प्रक्रिया को चलाएं या ईएफ।

    एक बाहरी फ़ाइल

    और निश्चित रूप से, में एसक्यूएल सर्वर से ब्लॉब डेटा निर्यात, एक ही विपरीत दिशा में सच है।

    पहले, मैं एक (7MB!) डेटाबेस रिकॉर्ड लोड और अपरिष्कृत-फाइल में अपनी बाइनरी डेटा लिखने के लिए कुछ सी #/LINQ कोड लिखा था। इसे चलाने के लिए लगभग 30-40 सेकंड लग गए।

    लेकिन अगर मैं एक फाइल करने के लिए SQL सर्वर डेटा निर्यात (SQL सर्वर मशीन पर बचाया) पहले ..

    EXEC master..xp_cmdshell 'BCP "select ef.ExtFile_Data FROM [External_File] ef where ExtFile_ID = 585" queryout "D:\ImportData\SomeExcelFile.xslx" -T -N' 
    

    ... और फिर उपयोगकर्ता के फ़ोल्डर में एसक्यूएल सर्वर फ़ोल्डर से फ़ाइल की प्रतिलिपि , फिर एक बार फिर, यह कुछ सेकंड में भाग गया।

    और ये मेरे सवाल है: क्यों दोनों LINQ और एफई डेटाबेस में एक बड़ा रिकॉर्ड डालने पर इतना बुरा कर रहे हैं?

    मुझे लगता है कि विलंबता (यूरोप में, यहां और यूरोप में हमारे डेटा केंद्र) के बीच की दूरी देरी का एक प्रमुख कारण है, लेकिन यह अजीब बात है कि एक बोग-मानक फ़ाइल-कॉपी इतनी तेज़ी से हो सकती है ।

    मैं कुछ याद आ रही है?

    जाहिर है, मुझे इन समस्याओं के लिए चलनेवाले मिल गए हैं, लेकिन उनमें हमारी SQL सर्वर मशीनों और SQL सर्वर मशीनों पर साझा फ़ोल्डर में कुछ अतिरिक्त अनुमतियां शामिल हैं, और हमारे डीबीए वास्तव में "xp_cmdshell जैसी चीज़ों के लिए अधिकार प्रदान करना पसंद नहीं करते हैं "...

    कुछ महीने बाद ...

    मैं एक ही मुद्दा फिर से इस सप्ताह था, और SQL में एक बड़े (6MB) रिकॉर्ड डालने के लिए बल्क में सम्मिलित उपयोग करने का प्रयास केविन एच के सुझाव सर्वर।

    थोक डालने का उपयोग करना, यह 6MB रिकॉर्ड सम्मिलित करने के चारों ओर 90 सेकंड ले लिया, भले ही हमारे डेटा सेंटर 6000 मील दूर है।

    तो, कहानी का नैतिक: बहुत बड़े डेटाबेस रिकॉर्ड डालने पर, नियमित SubmitChanges() कमांड का उपयोग करने से बचें, और थोक-सम्मिलन का उपयोग करने के लिए चिपके रहें।

  • +6

    एसक्यूएल सर्वर में 7MB फ़ाइलें सम्मिलित मत करो ... –

    +1

    उम्म ... आप दूरस्थ सर्वर से 7MB अपलोड कर रहे हैं। यह आपकी समस्या हो सकती है। – FreeAsInBeer

    +0

    हाँ, मैं होने जानते रिकॉर्ड डेटा की 7MB युक्त आदर्श से दूर है ... लेकिन मेरी बात है, एसक्यूएल सर्वर, LINQ/एफई खुशी से इस प्रकार का समर्थन इस सटीक परिदृश्य के लिए ब्लॉब डेटा प्रकार प्रदान करता है, लेकिन उनके प्रदर्शन घटिया है .. और मेरा सवाल है: * क्यों * प्रदर्शन इतना बुरा है ..?सर्वर पर फ़ाइल की प्रतिलिपि बनाने के बजाय एक रिकॉर्ड क्यों लिखना 20 गुना अधिक होगा? LINQ/EF का उपयोग करते हुए विलंबता ऐसी समस्या क्यों है? –

    उत्तर

    5

    आप प्रोफाइलर का उपयोग कर देखने के लिए इकाई की रूपरेखा डालने के साथ क्या कर रहा है की कोशिश कर सकते। उदाहरण के लिए, यदि यह आपकी तालिका से डेटा का चयन कर रहा है, तो तार पर डेटा वापस करने में लंबा समय लग सकता है, और आप स्थानीय रूप से नोटिस नहीं कर सकते हैं।

    मुझे पता चला है कि सी # से एसक्यूएल सर्वर में बड़ी मात्रा में डेटा (रिकॉर्ड गणना और रिकॉर्ड आकार दोनों) लोड करने का सबसे अच्छा तरीका SqlBulkCopy वर्ग का उपयोग करना है। भले ही आप केवल 1 रिकॉर्ड डाल रहे हों, फिर भी आप इस बदलाव से लाभ उठा सकते हैं।

    थोक प्रति का उपयोग करने के लिए, बस अपनी तालिका की संरचना से मेल खाने वाला एक डाटाटेबल बनाएं। फिर इस तरह कोड को कॉल करें।

    using (SqlConnection destinationConnection = new SqlConnection(connectionString)) 
    using (SqlBulkCopy bulkCopy = new SqlBulkCopy(destinationConnection)) 
    { 
        bulkCopy.DestinationTableName = "External_File"; 
        bulkCopy.WriteToServer(dataTable); 
    } 
    
    +0

    अरे। आप बिल्कुल सही कह रहे है। विडंबना यह है कि अतीत में मैंने वास्तव में दस्तावेज़ों के थोक-सम्मिलन * बहुत सारे * के लिए यह कैसे किया है, लेकिन मुझे एहसास नहीं हुआ कि यह भी एक विशाल रिकॉर्ड डालने के लिए उतना ही उपयोगी था। महान टिप, धन्यवाद! http://www.mikesknowledgebase.com/pages/LINQ/InsertAndDeletes.htm –

    संबंधित मुद्दे