سؤال

فيما يلي مثال على ما يحدث معي:

CREATE TABLE Parent (id BIGINT NOT NULL,
  PRIMARY KEY (id)) ENGINE=InnoDB;

CREATE TABLE Child (id BIGINT NOT NULL,
  parentid BIGINT NOT NULL,
  PRIMARY KEY (id),
  KEY (parentid),
  CONSTRAINT fk_parent FOREIGN KEY (parentid) REFERENCES Parent (id) ON DELETE CASCADE) ENGINE=InnoDB;

CREATE TABLE Uncle (id BIGINT NOT NULL,
  parentid BIGINT NOT NULL,
  childid BIGINT NOT NULL,
  PRIMARY KEY (id),
  KEY (parentid),
  KEY (childid),
  CONSTRAINT fk_parent_u FOREIGN KEY (parentid) REFERENCES Parent (id) ON DELETE CASCADE,
  CONSTRAINT fk_child FOREIGN KEY (childid) REFERENCES Child (id)) ENGINE=InnoDB;

لاحظ أنه لا يوجد ON DELETE CASCADE للعلاقة بين العم والطفل؛أي.لا يؤدي حذف الطفل إلى حذف عمه (أعمامه) والعكس صحيح.

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

  ERROR 1451 (23000): Cannot delete or update a parent row: a foreign key constraint fails (`cascade_test/uncle`, CONSTRAINT `fk_child` FOREIGN KEY (`childid`) REFERENCES `child` (`id`))

يحاول InnoDB حذف الطفل بشكل متتالي قبل العم (الأعمام) الذي يشير إليه.

هل فاتني شيء؟هذا هو مفترض أن تفشل لسبب لا أفهمه؟أم أن هناك بعض الحيل لإنجاحها (أم أنها خطأ في MySQL)؟

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

المحلول

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

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

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

نصائح أخرى

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

إذا كان حذف الطفل لا يؤدي إلى حذف أعمامه، فماذا يحدث بدلاً من ذلك؟لا يمكن أن يكون Uncle.childid فارغًا.

ما تريده هو أحد هذه الأشياء الثلاثة:

  1. يمكن أن يكون Uncle.childid فارغًا، وتريد ON DELETE SET NULL لـ Childid.
  2. لا يمكن أن يكون Uncle.childid فارغًا، وتريد تشغيل DELETE CASCADE لـ Childid.
  3. لا ينتمي Childid إلى Uncle، وتريد علاقة ChildsUncle مع قيود المفاتيح الخارجية ON DELETE CASCADE لكل من Child وUncle.سيكون Uncleid مفتاحًا مرشحًا لهذه العلاقة (أي.يجب أن تكون فريدة).

@ Matt Solnit أولاً وقبل كل شيء، يعد هذا سؤالًا جيدًا حقًا وبقدر ما أعرف متى سيتم حذف سجل من Parent، يحاول innodb أولاً تحديد الجداول الأخرى التي تحتوي على مراجع إليها حتى يتمكن من حذف السجل منها كـ حسنًا.في حالتك، يكون جدول الطفل وجدول العم، ويبدو الآن أنه في هذه الحالة قرر أولاً حذف السجل من الجدول التابع وبالتالي يكرر نفس العملية للطفل ويفشل في النهاية لأن العم يحمل إشارة إلى الجدول التابع ولكن لا "ON" تم تحديد "DELETE CASCADE" أو "ON DELETE SET NULL" لـ fk_child FK في جدول Uncle.ومع ذلك، يبدو ذلك لو يحاول innodb أولاً حذف السجل من جدول Uncle ومن ثم يجب أن يتم الحذف بسلاسة.حسنًا بعد التفكير مرة أخرى، أعتقد أنه نظرًا لأن innodb يتبع نموذج ACID، فإنه يختار Child بدلاً من Uncle لبدء عملية الحذف لأنه إذا بدأ بـ Uncle، فقد يكون الحذف في Child قد فشل على سبيل المثال.افترض أن جدول الصديق يحتوي على مفتاح fk_child (مشابه لـ Uncle) بدون ON DELETE CASCADE، والآن لا يزال هذا قد تسبب في فشل المعاملة بأكملها وبالتالي يبدو هذا السلوك الصحيح بالنسبة لي.وبعبارة أخرى، يبدأ innodb بجدول قد يتسبب في فشل محتمل في المعاملة ولكن هذه هي نظريتي في الواقع، قد تكون قصة مختلفة تمامًا.:)

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

select id from persons where -find all children of the grandparents
parent id in (
select parentid from persons --find the grandparents
where id in (
select parentid from persons --find the parents
where id=THECHILD) )
minus --and take out the child's parents
select parentid from persons
where id=THECHILD

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