سؤال

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

بعض المعلومات الأساسية التي قد تساعد في توضيح سبب طرح هذا السؤال:

  • تأتي البيانات فعليًا عبر خدمة الويب.تقوم خدمة الويب بكتابة ملف نصي إلى مجلد مشترك على خادم قاعدة البيانات والذي يقوم بدوره بتنفيذ عملية BULK INSERT.تم تنفيذ هذه العملية في الأصل على SQL Server 2000، وفي ذلك الوقت لم يكن هناك بديل آخر سوى التخلص من بضع مئات من INSERT البيانات على الخادم، والتي كانت في الواقع العملية الأصلية وكانت بمثابة كارثة في الأداء.

  • يتم إدراج البيانات بشكل مجمّع في جدول مرحلي دائم ثم يتم دمجها في جدول أكبر بكثير (بعد ذلك يتم حذفها من الجدول المرحلي).

  • كمية البيانات المطلوب إدراجها "كبيرة"، ولكنها ليست "ضخمة" - عادةً ما تكون بضع مئات من الصفوف، وربما تصل إلى 5-10 آلاف صف في حالات نادرة.لذلك شعوري الغريزي هو ذلك BULK INSERT لن يتم إجراء عملية غير مسجلة الذي - التي فرق كبير (ولكن بالطبع لست متأكدا، ومن هنا السؤال).

  • يعد الإدراج في الواقع جزءًا من عملية دفعية أكبر بكثير ويجب أن يحدث عدة مرات متتالية؛وبالتالي الأداء يكون شديد الأهمية.

الأسباب التي أود استبدالها BULK INSERT مع TVP هي:

  • من المحتمل أن كتابة الملف النصي عبر NetBIOS يستغرق بعض الوقت بالفعل، وهو أمر مروع جدًا من منظور معماري.

  • أعتقد أنه يمكن (ويجب) إلغاء جدول التدريج.السبب الرئيسي وراء ذلك هو أن البيانات المدرجة تحتاج إلى استخدامها لبضعة تحديثات أخرى في نفس وقت الإدراج، كما أن محاولة التحديث من جدول الإنتاج الضخم أكثر تكلفة بكثير من استخدام نظام مرحلي شبه فارغ طاولة.مع TVP، المعلمة في الأساس يكون الجدول المرحلي، يمكنني أن أفعل أي شيء أريده به قبل/بعد الإدخال الرئيسي.

  • يمكنني التخلص إلى حد كبير من عمليات التحقق من الخداع، وتعليمات التنظيف البرمجية، وجميع النفقات العامة المرتبطة بالإدراجات المجمعة.

  • لا داعي للقلق بشأن تنافس القفل على الجدول المرحلي أو tempdb إذا حصل الخادم على عدد قليل من هذه المعاملات في وقت واحد (نحاول تجنب ذلك، لكنه يحدث).

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

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


تحديث:الآن مع علامات استفهام أقل بنسبة 92%!

(الملقب ب:نتائج الإختبار)

والنتيجة النهائية هي الآن قيد الإنتاج بعد ما يبدو وكأنه عملية نشر مكونة من 36 مرحلة.تم اختبار كلا الحلين على نطاق واسع:

  • تمزيق رمز المجلد المشترك واستخدام SqlBulkCopy الطبقة مباشرة؛
  • التبديل إلى إجراء مخزن مع TVPs.

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

  1. ابدأ بتسلسل بيانات زمني يتراوح عادةً بين 20 و50 نقطة بيانات (على الرغم من أنه قد يصل في بعض الأحيان إلى بضع مئات)؛

  2. قم بإجراء مجموعة كاملة من المعالجة المجنونة عليها والتي تكون في الغالب مستقلة عن قاعدة البيانات.هذه العملية متوازية، لذلك تتم معالجة حوالي 8-10 من التسلسلات في (1) في نفس الوقت.كل عملية متوازية تولد 3 تسلسلات إضافية.

  3. خذ جميع التسلسلات الثلاثة والتسلسل الأصلي وقم بدمجها في دفعة واحدة.

  4. قم بدمج الدُفعات من جميع مهام المعالجة الـ 8 إلى 10 المنتهية الآن في دفعة واحدة كبيرة جدًا.

  5. قم باستيراده باستخدام إما BULK INSERT الإستراتيجية (راجع الخطوة التالية)، أو إستراتيجية TVP (انتقل إلى الخطوة 8).

  6. استخدم ال SqlBulkCopy فئة لتفريغ الدفعة الفائقة بأكملها في 4 جداول مرحلية دائمة.

  7. قم بتشغيل إجراء مخزن (أ) ينفذ مجموعة من خطوات التجميع على جدولين، بما في ذلك عدة JOIN الشروط، ثم (ب) ينفذ أ MERGE على 6 جداول إنتاج باستخدام البيانات المجمعة وغير المجمعة.(انتهى)

    أو

  8. توليد 4 DataTable الكائنات التي تحتوي على البيانات المراد دمجها؛3 منها تحتوي على أنواع CLR والتي لسوء الحظ لا يتم دعمها بشكل صحيح بواسطة ADO.NET TVPs، لذلك يجب إدخالها كتمثيلات سلسلة، مما يضر بالأداء قليلاً.

  9. قم بتغذية TVPs إلى إجراء مخزن، والذي يقوم بشكل أساسي بنفس المعالجة مثل (7)، ولكن مباشرة مع الجداول المستلمة.(انتهى)

وكانت النتائج متقاربة إلى حد معقول، ولكن أداء نهج TVP كان أفضل في المتوسط ​​في نهاية المطاف، حتى عندما تجاوزت البيانات 1000 صف بكمية صغيرة.

لاحظ أن عملية الاستيراد هذه يتم تشغيلها عدة آلاف من المرات متتالية، لذلك كان من السهل جدًا الحصول على متوسط ​​الوقت ببساطة عن طريق حساب عدد الساعات (نعم، الساعات) المستغرقة لإنهاء جميع عمليات الدمج.

في الأصل، كان متوسط ​​الدمج يستغرق حوالي 8 ثوانٍ تقريبًا (تحت الحمل العادي).إزالة NetBIOS kludge والتحويل إلى SqlBulkCopy خفضت الوقت إلى ما يقرب من 7 ثوان بالضبط.أدى التبديل إلى TVPs إلى تقليل الوقت اللازم لـ 5.2 ثانية لكل دفعة.هذا تحسن 35% في الإنتاجية لعملية يتم قياس وقت تشغيلها بالساعات - لذلك ليس سيئًا على الإطلاق.إنه أيضًا تحسن بنسبة 25٪ تقريبًا SqlBulkCopy.

أنا في الواقع واثق تمامًا من أن التحسن الحقيقي كان أكبر بكثير من هذا.أثناء الاختبار أصبح من الواضح أن الدمج النهائي لم يعد هو المسار الحرج؛وبدلاً من ذلك، بدأت خدمة الويب التي كانت تقوم بكل معالجة البيانات في الانكماش تحت عدد الطلبات الواردة.لم يتم تجاوز الحد الأقصى لوحدة المعالجة المركزية ولا الإدخال/الإخراج لقاعدة البيانات، ولم يكن هناك أي نشاط قفل مهم.في بعض الحالات، كنا نشهد فجوة تبلغ بضع ثوانٍ من الخمول بين عمليات الدمج المتعاقبة.كانت هناك فجوة طفيفة، ولكنها أصغر بكثير (نصف ثانية أو نحو ذلك) عند الاستخدام SqlBulkCopy.لكنني أعتقد أن ذلك سيصبح حكاية ليوم آخر.

خاتمة: إن أداء المعلمات ذات القيمة الجدولية أفضل حقًا من BULK INSERT عمليات لعمليات الاستيراد والتحويل المعقدة التي تعمل على مجموعات بيانات متوسطة الحجم.


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

بمعنى آخر، لدينا بالفعل ما يكفي من المعرفة حول العملية ولا نحتاج إلى جدول التدريج الآمن؛السبب الوحيد لوجود طاولة التدريج في المقام الأول هو تجنب الضرب على كل شيء INSERT و UPDATE العبارات التي كان علينا استخدامها بطريقة أخرى.في العملية الأصلية، كانت البيانات المرحلية موجودة فقط في الجدول المرحلي لأجزاء من الثانية على أي حال، لذلك لم تضيف أي قيمة من حيث الصيانة/قابلية الصيانة.

نلاحظ أيضا أن لدينا لا استبدال كل واحد BULK INSERT العمل مع TVPs.العديد من العمليات التي تتعامل مع كميات أكبر من البيانات و/أو لا تحتاج إلى القيام بأي شيء خاص مع البيانات بخلاف رميها في قاعدة البيانات لا تزال قيد الاستخدام SqlBulkCopy. أنا لا أقترح أن TVPs هي الدواء الشافي للأداء، ولكن فقط أنها نجحت SqlBulkCopy في هذه الحالة المحددة التي تتضمن عدة تحويلات بين التدريج الأولي والدمج النهائي.

إذن هذا هو الحال.تذهب النقطة إلى TToni للعثور على الرابط الأكثر صلة، لكنني أقدر الردود الأخرى أيضًا.شكرًا لك مرة أخرى!

هل كانت مفيدة؟

المحلول

ليس لدي خبرة حقًا في TVP حتى الآن، ولكن يوجد مخطط جيد لمقارنة الأداء مقابل TVP.إدراج مجمع في MSDN هنا.

يقولون أن BULK INSERT لديه تكلفة أعلى لبدء التشغيل، ولكنه أسرع بعد ذلك.في سيناريو العميل البعيد، يقومون برسم الخط عند حوالي 1000 صف (لمنطق الخادم "البسيط").انطلاقًا من وصفهم، أود أن أقول إنه يجب أن تكون جيدًا في استخدام TVP.من المحتمل أن يكون الأداء الناجح - إن وجد - ضئيلًا وتبدو الفوائد المعمارية جيدة جدًا.

يحرر:في ملاحظة جانبية، يمكنك تجنب الملف المحلي للخادم والاستمرار في استخدام النسخة المجمعة باستخدام كائن SqlBulkCopy.ما عليك سوى ملء DataTable وإدخاله في طريقة "WriteToServer" لمثيل SqlBulkCopy.سهل الاستخدام، وسريع جدًا.

نصائح أخرى

يجب أن يؤخذ المخطط المذكور فيما يتعلق بالارتباط المقدم في إجابة @ TToni في السياق.لست متأكدًا من مقدار البحث الفعلي الذي تم إجراؤه في هذه التوصيات (لاحظ أيضًا أن المخطط يبدو متاحًا فقط في 2008 و 2008 R2 إصدارات تلك الوثائق).

ومن ناحية أخرى، توجد هذه الوثيقة التقنية من الفريق الاستشاري لعملاء SQL Server: تعظيم الإنتاجية مع TVP

لقد كنت أستخدم TVPs منذ عام 2009 ووجدت، على الأقل في تجربتي، أنه بالنسبة لأي شيء آخر غير الإدراج البسيط في جدول الوجهة دون أي احتياجات منطقية إضافية (وهو ما نادرًا ما يحدث على الإطلاق)، فإن TVPs هي الخيار الأفضل عادةً.

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

فيما يتعلق بالاختبار الذي تم إجراؤه في السؤال، أعتقد أنه يمكن إثبات أنه أسرع مما تم العثور عليه في الأصل:

  1. يجب ألا تستخدم DataTable، إلا إذا كان تطبيقك يستخدمه خارج نطاق إرسال القيم إلى TVP.باستخدام IEnumerable<SqlDataRecord> الواجهة أسرع وتستخدم ذاكرة أقل لأنك لا تقوم بتكرار المجموعة في الذاكرة فقط لإرسالها إلى قاعدة البيانات.وقد قمت بتوثيق ذلك في الأماكن التالية:
  2. TVPs هي متغيرات الجدول، وبالتالي لا تحتفظ بالإحصائيات.بمعنى أنهم يقومون بالإبلاغ عن وجود صف واحد فقط إلى "مُحسِّن الاستعلام".لذا، في إجراءاتك، إما:
    • استخدم إعادة الترجمة على مستوى البيان في أي استعلامات تستخدم TVP لأي شيء آخر غير التحديد البسيط: OPTION (RECOMPILE)
    • قم بإنشاء جدول مؤقت محلي (أي.أعزب #) وانسخ محتويات TVP إلى الجدول المؤقت

أعتقد أنني سأظل متمسكًا بنهج الإدراج المجمع.قد تجد أن tempdb ما زال يتعرض للضرب باستخدام TVP مع عدد معقول من الصفوف.هذا هو شعوري الغريزي، لا أستطيع أن أقول إنني اختبرت أداء استخدام TVP (أنا مهتم بسماع آراء الآخرين أيضًا)

لم تذكر ما إذا كنت تستخدم .NET، ولكن النهج الذي اتبعته لتحسين الحلول السابقة كان هو القيام بتحميل مجمع للبيانات باستخدام نسخة سكلبولك فئة - لا تحتاج إلى كتابة البيانات في الملف أولاً قبل التحميل، فقط قم بإعطاء الأمر نسخة سكلبولك فئة (على سبيل المثال) DataTable - هذه هي أسرع طريقة لإدراج البيانات في قاعدة البيانات.من 5 إلى 10 آلاف صف ليس كثيرًا، لقد استخدمت هذا لما يصل إلى 750 ألف صف.أظن أنه بشكل عام، مع بضع مئات من الصفوف، لن يحدث فرقًا كبيرًا باستخدام TVP.ولكن التوسع سيكون محدودا IMHO.

وربما الجديد دمج هل الوظائف في SQL 2008 تفيدك؟

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

لاحظ أنه يمكنك تحسين التحميل في جدول التدريج هذا، عن طريق ملؤه بدون أي فهارس.ثم بمجرد ملؤها، أضف أي فهارس مطلوبة عند هذه النقطة (FILLFACTOR=100 للحصول على أداء القراءة الأمثل، حيث لن يتم تحديثه في هذه المرحلة).

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

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

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

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

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