كيفية تغيير الإدخالات ذات المعلمات البطيئة إلى نسخة مجمعة سريعة (حتى من الذاكرة)

StackOverflow https://stackoverflow.com/questions/127152

سؤال

كان لدي شيء مثل هذا في الكود الخاص بي (.Net 2.0، MS SQL)

SqlConnection connection = new SqlConnection(@"Data Source=localhost;Initial
Catalog=DataBase;Integrated Security=True");
  connection.Open();

  SqlCommand cmdInsert = connection.CreateCommand();
  SqlTransaction sqlTran = connection.BeginTransaction();
  cmdInsert.Transaction = sqlTran;

  cmdInsert.CommandText =
     @"INSERT INTO MyDestinationTable" +
      "(Year, Month, Day, Hour,  ...) " +
      "VALUES " +
      "(@Year, @Month, @Day, @Hour, ...) ";

  cmdInsert.Parameters.Add("@Year", SqlDbType.SmallInt);
  cmdInsert.Parameters.Add("@Month", SqlDbType.TinyInt);
  cmdInsert.Parameters.Add("@Day", SqlDbType.TinyInt);
  // more fields here
  cmdInsert.Prepare();

  Stream stream = new FileStream(fileName, FileMode.Open, FileAccess.Read);

  StreamReader reader = new StreamReader(stream);
  char[] delimeter = new char[] {' '};
  String[] records;
  while (!reader.EndOfStream)
  {
    records = reader.ReadLine().Split(delimeter, StringSplitOptions.None);

    cmdInsert.Parameters["@Year"].Value = Int32.Parse(records[0].Substring(0, 4));
    cmdInsert.Parameters["@Month"].Value = Int32.Parse(records[0].Substring(5, 2));
    cmdInsert.Parameters["@Day"].Value = Int32.Parse(records[0].Substring(8, 2));
    // more here complicated stuff here
    cmdInsert.ExecuteNonQuery()
  }
  sqlTran.Commit();
  connection.Close();

مع cmdInsert.ExecuteNonQuery() تم التعليق على تنفيذ هذا الرمز في أقل من ثانيتين.مع تنفيذ SQL، يستغرق الأمر دقيقة و20 ثانية.هناك حوالي 0.5 مليون سجل.يتم إفراغ الجدول من قبل.تستغرق مهمة تدفق بيانات SSIS ذات الوظائف المماثلة حوالي 20 ثانية.

  • إدراج السائبة كان ليس خيارا (انظر أدناه).لقد فعلت بعض الأشياء الفاخرة خلال هذا الاستيراد.
  • جهاز الاختبار الخاص بي هو Core 2 Duo مع 2 جيجابايت من ذاكرة الوصول العشوائي.
  • عند البحث في إدارة المهام، لم يتم تشغيل وحدة المعالجة المركزية (CPU) بشكل كامل.يبدو أيضًا أن IO لم يتم استخدامه بالكامل.
  • المخطط بسيط مثل الجحيم:جدول واحد به AutoInt كمؤشر أساسي وأقل من 10 ints، int صغيرة وأحرف (10).

بعد بعض الإجابات هنا وجدت أنه من الممكن التنفيذ نسخة بالجملة من الذاكرة!كنت أرفض استخدام النسخة المجمعة لأنني اعتقدت أنه يجب أن يتم ذلك من الملف ...

الآن أستخدم هذا ويستغرق حوالي 20 ثانية (مثل مهمة SSIS)

  DataTable dataTable = new DataTable();

  dataTable.Columns.Add(new DataColumn("ixMyIndex", System.Type.GetType("System.Int32")));   
  dataTable.Columns.Add(new DataColumn("Year", System.Type.GetType("System.Int32")));   
  dataTable.Columns.Add(new DataColumn("Month", System.Type.GetType("System.Int32")));
  dataTable.Columns.Add(new DataColumn("Day", System.Type.GetType("System.Int32")));
 // ... and more to go

  DataRow dataRow;
  object[] objectRow = new object[dataTable.Columns.Count];

  Stream stream = new FileStream(fileName, FileMode.Open, FileAccess.Read);

  StreamReader reader = new StreamReader(stream);
  char[] delimeter = new char[] { ' ' };
  String[] records;
  int recordCount = 0;
  while (!reader.EndOfStream)
  {
    records = reader.ReadLine().Split(delimeter, StringSplitOptions.None);

    dataRow = dataTable.NewRow();
    objectRow[0] = null; 
    objectRow[1] = Int32.Parse(records[0].Substring(0, 4));
    objectRow[2] = Int32.Parse(records[0].Substring(5, 2));
    objectRow[3] = Int32.Parse(records[0].Substring(8, 2));
    // my fancy stuf goes here

    dataRow.ItemArray = objectRow;         
    dataTable.Rows.Add(dataRow);

    recordCount++;
  }

  SqlBulkCopy bulkTask = new SqlBulkCopy(connection, SqlBulkCopyOptions.TableLock, null);
  bulkTask.DestinationTableName = "MyDestinationTable"; 
  bulkTask.BatchSize = dataTable.Rows.Count;
  bulkTask.WriteToServer(dataTable);
  bulkTask.Close();
هل كانت مفيدة؟

المحلول

بدلاً من إدراج كل سجل على حدة، حاول استخدام نسخة سكلبولك فئة لإدراج جميع السجلات بشكل مجمّع مرة واحدة.

أنشئ DataTable وأضف جميع سجلاتك إلى DataTable، ثم استخدمه نسخة سكلبولك.اكتب إلى الخادم لإدراج كافة البيانات دفعة واحدة.

نصائح أخرى

هل تشترط الصفقة؟يحتاج استخدام المعاملة إلى موارد أكثر بكثير من الأوامر البسيطة.

أيضًا إذا كنت متأكدًا من صحة القيم المدرجة، فيمكنك استخدام BulkInsert.

دقيقة واحدة تبدو معقولة جدًا لـ 0.5 مليون سجل.وهذا رقم قياسي كل 0.00012 ثانية.

هل يحتوي الجدول على أي فهارس؟ستؤدي إزالة هذه العناصر وإعادة تطبيقها بعد الإدخال المجمع إلى تحسين أداء الإدخالات، إذا كان هذا خيارًا.

لا يبدو من غير المعقول بالنسبة لي معالجة 8333 سجلًا في الثانية... ما نوع الإنتاجية التي تتوقعها؟

إذا كنت بحاجة إلى سرعة أفضل، فقد تفكر في تنفيذ إدراج مجمع:

http://msdn.microsoft.com/en-us/library/ms188365.aspx

إذا لم يكن هناك خيار من أشكال الإدراج المجمع، فإن الطريقة الأخرى ستكون سلاسل رسائل متعددة، لكل منها اتصالها الخاص بقاعدة البيانات.

المشكلة في النظام الحالي هي أن لديك 500000 رحلة ذهابًا وإيابًا إلى قاعدة البيانات، وتنتظر اكتمال الرحلة الأولى ذهابًا وإيابًا قبل البدء في الرحلة التالية - أي نوع من زمن الوصول (على سبيل المثال، شبكة بين الأجهزة) سيعني أن معظم وقتك يقضي في الانتظار.

إذا كان بإمكانك تقسيم المهمة، ربما باستخدام شكل ما من أشكال إعداد المنتج/المستهلك، فقد تجد أنه يمكنك الاستفادة بشكل أكبر من جميع الموارد.

ومع ذلك، للقيام بذلك، سيتعين عليك خسارة المعاملة الكبيرة - وإلا فإن مؤشر ترابط الكاتب الأول سيحظر جميع المعاملات الأخرى حتى تكتمل معاملته.لا يزال بإمكانك استخدام المعاملات، ولكن سيتعين عليك استخدام الكثير من المعاملات الصغيرة بدلاً من معاملة واحدة كبيرة.

سيكون SSIS سريعًا لأنه يستخدم طريقة الإدراج المجمع - قم بإجراء كل المعالجة المعقدة أولاً، وقم بإنشاء القائمة النهائية للبيانات المراد إدراجها وإعطائها كلها في نفس الوقت للإدراج المجمع.

أفترض أن ما يستغرق حوالي 58 ثانية هو الإدخال الفعلي لـ 500000 سجل - لذا فإنك تحصل على حوالي 10000 إدخال في الثانية.بدون معرفة مواصفات جهاز خادم قاعدة البيانات لديك (أرى أنك تستخدم مضيفًا محليًا، لذلك لا ينبغي أن يمثل تأخير الشبكة مشكلة)، فمن الصعب تحديد ما إذا كان هذا جيدًا أم سيئًا أم سيئًا.

أود أن ألقي نظرة على مخطط قاعدة البيانات الخاصة بك - هل هناك مجموعة من المؤشرات في الجدول والتي يجب تحديثها بعد كل إدراج؟قد يكون هذا من جداول أخرى تحتوي على مفاتيح خارجية تشير إلى الجدول الذي تعمل عليه.هناك أدوات ملفات تعريف SQL ومرافق لمراقبة الأداء مضمنة في SQL Server، لكنني لم أستخدمها مطلقًا.لكنها قد تظهر مشاكل مثل الأقفال وأشياء من هذا القبيل.

قم بالأشياء الفاخرة فيما يتعلق بالبيانات، على جميع السجلات أولاً.ثم قم بإدراجها بشكل مجمع.

(نظرًا لأنك لا تقوم بالاختيار بعد الإدراج ..لا أرى مشكلة في تطبيق كافة العمليات على البيانات قبل BulkInsert

إذا كان علي أن أخمن، فإن أول شيء سأبحث عنه هو وجود عدد كبير جدًا من الفهارس أو النوع الخاطئ من الفهارس في جدول tbTrafficLogTTL.بدون النظر إلى تعريف المخطط للجدول، لا أستطيع أن أقول ذلك حقًا، لكنني واجهت مشكلات أداء مماثلة عندما:

  1. المفتاح الأساسي هو GUID والفهرس الأساسي متفاوت.
  2. يوجد نوع من الفهرس الفريد في مجموعة من الحقول.
  3. يوجد عدد كبير جدًا من الفهارس على الطاولة.

عندما تبدأ بفهرسة نصف مليون صف من البيانات، فإن الوقت المستغرق لإنشاء الفهارس وصيانتها يتزايد.

سألاحظ أيضًا أنه إذا كان لديك أي خيار لتحويل حقول السنة والشهر واليوم والساعة والدقيقة والثانية إلى حقل تاريخ ووقت 2 واحد أو حقل طابع زمني واحد، فيجب عليك ذلك.أنت تضيف الكثير من التعقيد إلى بنية البيانات الخاصة بك، دون أي مكاسب.السبب الوحيد الذي يجعلني أفكر في استخدام بنية حقل منقسمة مثل هذا هو إذا كنت تتعامل مع مخطط قاعدة بيانات موجود مسبقًا ولا يمكن تغييره لأي سبب من الأسباب.في هذه الحالة، من السيئ أن تكون أنت.

واجهت مشكلة مماثلة في عقدي الأخير.أنت تقوم بـ 500000 رحلة إلى SQL لإدراج بياناتك.للحصول على زيادة كبيرة في الأداء، تحتاج إلى التحقق من أسلوب BulkInsert في مساحة اسم SQL.لقد أجريت عمليات "إعادة تحميل" استغرقت أكثر من ساعتين لاستعادة عشرات الجداول وصولاً إلى 31 ثانية بمجرد تنفيذ الاستيراد المجمع.

من الأفضل تحقيق ذلك باستخدام شيء مثل الأمر bcp.إذا لم يكن ذلك متاحًا، فإن الاقتراحات المذكورة أعلاه حول استخدام BULK INSERT هي أفضل رهان لك.أنت تقوم بـ 500000 رحلة ذهابًا وإيابًا إلى قاعدة البيانات وتكتب 500000 إدخال في ملفات السجل، ناهيك عن أي مساحة تحتاج إلى تخصيصها لملف السجل والجدول والفهارس.

إذا كنت تقوم بالإدراج بترتيب مختلف عن الفهرس المجمع، فسيتعين عليك أيضًا التعامل مع الوقت اللازم لإعادة تنظيم البيانات الفعلية على القرص.يوجد الكثير من المتغيرات هنا والتي من المحتمل أن تجعل استعلامك يعمل بشكل أبطأ مما تريد.

~ 10000 معاملة في الثانية ليست أمرًا سيئًا بالنسبة للإدخالات الفردية القادمة من الكود/

إدراج مجمع = bcp من إذن

يمكنك دفع الإدراج لتقليل Roundtrips SqldataAdaptor.UpDatebatchSize = 10000 يعطي 50 رحلة دائرية

لا يزال لديك 500 ألف إدراج بالرغم من ذلك ...

شرط

MSDN

مرخصة بموجب: CC-BY-SA مع الإسناد
لا تنتمي إلى StackOverflow
scroll top