2014-10-22 7 views
6

के समानांतर में एक बड़ी सीएसवी फ़ाइल निर्यात करें मेरे पास एक बड़ी सीएसवी फ़ाइल है ... 10 कॉलम, 100 मिलियन पंक्तियां, मेरी हार्ड डिस्क पर लगभग 6 जीबी आकार। मैं इस सीएसवी फ़ाइल लाइन को लाइन से पढ़ना चाहता हूं और फिर SQL थोक प्रतिलिपि का उपयोग कर डेटा को Microsoft SQL सर्वर डेटाबेस में लोड करना चाहता हूं। मैंने यहां और इंटरनेट पर भी कुछ धागे पढ़े हैं। ज्यादातर लोग सुझाव देते हैं कि समांतर में एक सीएसवी फ़ाइल पढ़ने से दक्षता के मामले में ज्यादा खरीद नहीं होती है क्योंकि कार्य/थ्रेड डिस्क एक्सेस के लिए संघर्ष करते हैं।एसक्यूएल सर्वर

जो मैं करने की कोशिश कर रहा हूं वह है, सीएसवी से लाइन द्वारा लाइन पढ़ें और आकार 100K पंक्तियों के संग्रह को अवरुद्ध करने के लिए इसे जोड़ें। और एक बार यह संग्रह SQLBuckCopy API का उपयोग कर SQL सर्वर पर डेटा लिखने के लिए एक नया कार्य/थ्रेड पूर्ण हो गया है।

मैंने कोड का यह टुकड़ा लिखा है, लेकिन रन टाइम पर एक त्रुटि मार रहा है जो कहता है "लंबित ऑपरेशन वाले ऑब्जेक्ट पर थोक प्रतिलिपि लगाने का प्रयास।" यह परिदृश्य ऐसा कुछ दिखता है जिसे आसानी से .NET 4.0 TPL का उपयोग करके हल किया जा सकता है लेकिन मैं इसे काम नहीं कर पा रहा हूं। मैं क्या गलत कर रहा हूँ पर कोई सुझाव?

public static void LoadCsvDataInParalleToSqlServer(string fileName, string connectionString, string table, DataColumn[] columns, bool truncate) 
    { 
     const int inputCollectionBufferSize = 1000000; 
     const int bulkInsertBufferCapacity = 100000; 
     const int bulkInsertConcurrency = 8; 

     var sqlConnection = new SqlConnection(connectionString); 
     sqlConnection.Open(); 

     var sqlBulkCopy = new SqlBulkCopy(sqlConnection.ConnectionString, SqlBulkCopyOptions.TableLock) 
     { 
      EnableStreaming = true, 
      BatchSize = bulkInsertBufferCapacity, 
      DestinationTableName = table, 
      BulkCopyTimeout = (24 * 60 * 60), 
     }; 

     BlockingCollection<DataRow> rows = new BlockingCollection<DataRow>(inputCollectionBufferSize); 
     DataTable dataTable = new DataTable(table); 
     dataTable.Columns.AddRange(columns); 

     Task loadTask = Task.Factory.StartNew(() => 
      { 
       foreach (DataRow row in ReadRows(fileName, dataTable)) 
       { 
        rows.Add(row); 
       } 

       rows.CompleteAdding(); 
      }); 

     List<Task> insertTasks = new List<Task>(bulkInsertConcurrency); 

     for (int i = 0; i < bulkInsertConcurrency; i++) 
     { 
      insertTasks.Add(Task.Factory.StartNew((x) => 
       { 
        List<DataRow> bulkInsertBuffer = new List<DataRow>(bulkInsertBufferCapacity); 

        foreach (DataRow row in rows.GetConsumingEnumerable()) 
        { 
         if (bulkInsertBuffer.Count == bulkInsertBufferCapacity) 
         { 
          SqlBulkCopy bulkCopy = x as SqlBulkCopy; 
          var dataRows = bulkInsertBuffer.ToArray(); 
          bulkCopy.WriteToServer(dataRows); 
          Console.WriteLine("Inserted rows " + bulkInsertBuffer.Count); 
          bulkInsertBuffer.Clear(); 
         } 

         bulkInsertBuffer.Add(row); 
        } 

       }, 
       sqlBulkCopy)); 
     } 

     loadTask.Wait(); 
     Task.WaitAll(insertTasks.ToArray()); 
    } 

    private static IEnumerable<DataRow> ReadRows(string fileName, DataTable dataTable) 
    { 
     using (var textFieldParser = new TextFieldParser(fileName)) 
     { 
      textFieldParser.TextFieldType = FieldType.Delimited; 
      textFieldParser.Delimiters = new[] { "," }; 
      textFieldParser.HasFieldsEnclosedInQuotes = true; 

      while (!textFieldParser.EndOfData) 
      { 
       string[] cols = textFieldParser.ReadFields(); 

       DataRow row = dataTable.NewRow(); 

       for (int i = 0; i < cols.Length; i++) 
       { 
        if (string.IsNullOrEmpty(cols[i])) 
        { 
         row[i] = DBNull.Value; 
        } 
        else 
        { 
         row[i] = cols[i]; 
        } 
       } 

       yield return row; 
      } 
     } 
    } 
+9

अपने स्वयं के टूल लिखने में समय बिताने के बजाय, एक ईटीएल उपकरण का उपयोग क्यों न करें जो पहले से ही SQL सर्वर एकीकरण सेवाओं जैसे ऐसा करता है। –

+2

क्या आपने इस कोड के अनुक्रमिक संस्करण की कोशिश की है और क्या आपने साबित किया है कि बहु-थ्रेडिंग की जटिलता प्रदर्शन लाभ के लायक है? – Cory

+2

थोक आवेषण को अनुकूलित करने के लिए कई ऑनलाइन मार्गदर्शिकाएं हैं, यानी http://technet.microsoft.com/en-us/library/ms190421(v=sql.105).aspx। ऐसा लगता है कि आप ऐसी समस्या को हल करने की कोशिश कर रहे हैं जिसे आपने साबित नहीं किया है। मेरा सुझाव है कि आप पहले 'बीसीपी.एक्सईई' का उपयोग करके बेसलाइन प्राप्त करें और फिर उस समय कोशिश करें और सुधार करें। –

उत्तर

6

मत करो।

समांतर पहुंच फ़ाइल के तेज़ी से पढ़ने या आपको नहीं दे सकती है (यह नहीं होगा, लेकिन मैं से लड़ने वाला नहीं हूं युद्ध ...) लेकिन कुछ समानांतर लिखने के लिए यह आपको नहीं देगा तेजी से थोक डालने। ऐसा इसलिए है क्योंकि कम से कम लॉग इन थोक डालने (यानी वास्तव में तेज़ थोक डालने) को एक टेबल लॉक की आवश्यकता होती है। Prerequisites for Minimal Logging in Bulk Import देखें:

मिनिमल प्रवेश की आवश्यकता है कि लक्ष्य तालिका निम्न शर्तों को पूरा करता:

...
- टेबल ताला निर्दिष्ट किया जाता है (TABLOCK का उपयोग)।
...

समानांतर आवेषण, परिभाषा के द्वारा, समवर्ती तालिका ताले प्राप्त नहीं कर सकता। QED। आप गलत पेड़ को भड़क रहे हैं।

अपने स्रोतों को इंटरनेट पर यादृच्छिक खोज से रोकना बंद करें। The Data Loading Performance Guide पढ़ें, मार्गदर्शिका है ... प्रदर्शन डेटा लोडिंग।

मैं आपको पहिया का आविष्कार करना बंद करने की सलाह दूंगा। SSIS का उपयोग करें, यह है जिसे संभालने के लिए डिज़ाइन किया गया है।

+0

ठीक से दूरस्थ पहुंच नहीं है। क्या आप मुझे एक मौजूदा एसएसआईएस पैकेज पर इंगित कर सकते हैं कि एक सीएसवी फ़ाइल से डेटा को एसक्यूएल टेबल में सम्मिलित करता है? जैसा कि आपने कहा था, मैं पहिया को पुन: आविष्कार नहीं करना चाहता हूं और इसलिए अपने – user330612

+0

पर एसएसआईएस पैकेज बनाने के बजाय कुछ पूर्व-मौजूदा समाधान का उपयोग करना चाहूंगा, आपको बस एक [फ्लैट फ़ाइल स्रोत] (http: // msdn .microsoft.com/en-us/library/ms139941.aspx) फास्ट-लोड सेट के साथ एक [ओलेडीबी गंतव्य] (http://msdn.microsoft.com/en-us/library/ms141237.aspx) से जुड़ा हुआ है। [एसएसआईएस का उपयोग कर डेटाबेस तालिका में आयात सीएसवी फ़ाइल देखें] (http://blog.sqlauthority.com/2011/05/12/sql-server-import-csv-file-into-डेटा-table-using-ssis/), उदाहरण के लिए। –

+0

>> लेकिन कुछ समानांतर लिखने के लिए यह आपको तेजी से थोक सम्मिलित नहीं करेगा। << यह सच नहीं है। मैंने टेबल लॉकिंग के साथ कुछ समान, पावरशेल रनस्पेस का इस्तेमाल किया, और मैं 90,000 पंक्तियों/सेकंड से 140,000 पंक्तियों/सेकंड तक चला गया। –

5

http://joshclose.github.io/CsvHelper/

https://efbulkinsert.codeplex.com/

यदि आप के लिए संभव है, मैं सुझाव है कि आप एक सूची < टी> ऊपर उल्लिखित csvhelper का उपयोग करके अपने फ़ाइल को पढ़ने और अपने डाटाबेस थोक डालने का उपयोग करने के बारे में के रूप में आप क्या कर रहे हैं या efbulkinsert जो मैंने उपयोग किया है और आश्चर्यजनक रूप से तेज़ है।

using CsvHelper; 

public static List<T> CSVImport<T,TClassMap>(string csvData, bool hasHeaderRow, char delimiter, out string errorMsg) where TClassMap : CsvHelper.Configuration.CsvClassMap 
    { 
     errorMsg = string.Empty; 
     var result = Enumerable.Empty<T>(); 

     MemoryStream memStream = new MemoryStream(Encoding.UTF8.GetBytes(csvData)); 
     StreamReader streamReader = new StreamReader(memStream); 
     var csvReader = new CsvReader(streamReader); 

     csvReader.Configuration.RegisterClassMap<TClassMap>(); 
     csvReader.Configuration.DetectColumnCountChanges = true; 
     csvReader.Configuration.IsHeaderCaseSensitive = false; 
     csvReader.Configuration.TrimHeaders = true; 
     csvReader.Configuration.Delimiter = delimiter.ToString(); 
     csvReader.Configuration.SkipEmptyRecords = true; 
     List<T> items = new List<T>(); 

     try 
     { 
      items = csvReader.GetRecords<T>().ToList(); 
     } 
     catch (Exception ex) 
     { 
      while (ex != null) 
      { 
       errorMsg += ex.Message + Environment.NewLine; 

       foreach (var val in ex.Data.Values) 
        errorMsg += val.ToString() + Environment.NewLine; 

       ex = ex.InnerException; 
      } 
     } 
     return items; 
    } 
} 

संपादित करें - मुझे समझ में नहीं आता कि आप थोक सम्मिलन के साथ क्या कर रहे हैं। आप पूरी सूची या डेटा डेटा तालिका को थोक करना चाहते हैं, पंक्ति-दर-पंक्ति नहीं।

+0

सीएसवी बहुत बड़ा प्रतीत होता है। (6 जीबी)। क्या 'GetRecords ().ToList()' स्मृति को सबकुछ लोड करता है? – AechoLiu

+0

हाँ - अच्छा बिंदु, यह उसके लिए संभव नहीं हो सकता है। एक गिल्प में थोक प्रविष्टि एक बड़ा टाइमवेवर था। शायद वह सूची में टेक() को कॉल करने के लिए इसे थोड़ा सा कॉल कर सकता है। उनकी सूची File.ReadLines द्वारा बनाई गई स्ट्रिंग में फिट होने लगती है। – Sam

+0

मैं आकार की वजह से पूरी सीएसवी फ़ाइल को स्मृति में लोड नहीं कर सकता। तो मुझे एक समय में 100 के लाइनों को पढ़ने की जरूरत है और फिर इसे bulkinsert का उपयोग कर SQL सर्वर पर लिखना होगा। और हाँ, मैं एक ही समय में एक ही पंक्ति में पूरी तालिका लिखना नहीं चाहता हूं। – user330612

3

आप दुकान प्रक्रिया बना सकते हैं और तरह

नीचे
CREATE PROCEDURE [dbo].[CSVReaderTransaction] 
    @Filepath varchar(100)='' 
AS 
-- STEP 1: Start the transaction 
BEGIN TRANSACTION 

-- STEP 2 & 3: checking @@ERROR after each statement 
EXEC ('BULK INSERT Employee FROM ''' [email protected] 
     +''' WITH (FIELDTERMINATOR = '','', ROWTERMINATOR = ''\n'')') 

-- Rollback the transaction if there were any errors 
IF @@ERROR <> 0 
BEGIN 
    -- Rollback the transaction 
    ROLLBACK 

    -- Raise an error and return 
    RAISERROR ('Error in inserting data into employee Table.', 16, 1) 
    RETURN 
END 

COMMIT TRANSACTION 

तुम भी FIELDTERMINATOR और ROWTERMINATOR तरह BATCHSIZE विकल्प जोड़ सकते हैं फ़ाइल स्थान पारित कर सकते हैं।

+0

मेरे लिए काम नहीं करता है, फाइल मेरी स्थानीय मशीन पर है और SQL सर्वर एक अलग मशीन पर है और उस मशीन के पास मेरे स्थानीय ड्राइव – user330612

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