سؤال

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

فيما يلي رسم تخطيطي أساسي لجداول قاعدة البيانات المعنية.

// table "package"
// columns packageID, policyID, other data...
// 
// table "insurancepolicy"
// columns policyID, coverageAmount, other data...

فيما يلي رسم تخطيطي أساسي لما أريد القيام به:

using (SqlConnection conn = new SqlConnection(...))
{
  sqlTransaction sqlTrans = conn.BeginTransaction(IsolationLevel.RepeatableRead);

  // Calls a stored procedure that checks if the foreign key in the transaction table has a value.
  if (PackageDB.HasInsurancePolicy(packageID, conn))
  { 
    sqlTrans.Commit();
    return false;
  }

  // Insert row in foreign table.
  int policyID = InsurancePolicyDB.Insert(coverageAmount, conn);
  if (policyID <= 0)
  {
    sqlTrans.Rollback();
    return false;
  }

  // Assign foreign key to parent table.  If this fails, roll back everything.
  bool assigned = PackageDB.AssignPolicyID(packageID, policyID, conn);
  if (!assigned)
  {
    sqlTrans.Rollback();
    return false;
  }
}

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

ملحوظة:بسبب تصميم قاعدة بيانات CRUD، تتضمن كل الإجراءات المخزنة إما قراءة (تحديد)، أو إنشاء (إدراج)، أو تحديث.

هل هذا هو الاستخدام الصحيح لعزل المعاملات RepeatableRead؟

شكرًا.

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

المحلول

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

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

begin tran (read committed)

/* tentatively insert new Policy */
insert Policy

/* attach Package to Policy if it's still free */
update Package
  set Package.policy_id = @policy_id
  where Package.package_id = @package_id and Package.policy_id is null

if @@rowcount > 0
  commit
else
  rollback

يعمل هذا بشكل أفضل عندما تكون الصراعات نادرة، ويبدو أن هذه هي حالتك.

نصائح أخرى

أعتقد أنك تريد بالفعل مستوى عزل قابل للتسلسل.تكمن المشكلة في أنه يمكن لموضوعين تجاوز HasInsurancePolicyCheck (على الرغم من أنه ليس لدي أي فكرة عما سيفعله InsurancePolicyDB.Insert أو سبب إرجاعه 0)

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

وأنا أتفق مع فكرة "قائمة انتظار الرسائل" في رد آرونجنسن.إذا كنت قلقًا بشأن عدة سلاسل رسائل متزامنة تحاول تحديث نفس صف البيانات في وقت واحد، فيجب عليك بدلاً من ذلك أن تجعل سلاسل الرسائل تقوم بإدراج بياناتها في قائمة انتظار العمل، والتي تتم معالجتها بعد ذلك بالتسلسل بواسطة مؤشر ترابط واحد.يؤدي هذا إلى تقليل التنافس على قاعدة البيانات بشكل كبير، لأنه يتم تحديث الجدول الهدف بواسطة مؤشر ترابط واحد فقط بدلاً من "N"، وتقتصر عمليات قائمة انتظار العمل على الإدراجات بواسطة سلاسل الرسائل، والقراءة/التحديث بواسطة مؤشر ترابط معالجة البيانات.

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