[{"data":1,"prerenderedAt":-1},["ShallowReactive",2],{"blog-post-your-foreign-keys-are-not-protecting-your-eav-data":3},{"name":4,"created_at":5,"published_at":6,"updated_at":7,"id":8,"uuid":9,"content":10,"slug":117,"full_slug":118,"sort_by_date":119,"position":120,"tag_list":121,"is_startpage":122,"parent_id":123,"meta_data":119,"group_id":124,"first_published_at":6,"release_id":119,"lang":125,"path":119,"alternates":126,"default_full_slug":119,"translated_slugs":119},"Your foreign keys are not protecting your EAV data","2026-09-03T12:59:28.488Z","2026-09-03T12:59:28.537Z","2026-09-03T12:59:28.551Z",216067147757723,"fc72325e-7747-4998-aa5d-e07a8e39d852",{"_uid":11,"body":12,"title":4,"excerpt":114,"component":115,"publish_at":116},"e5e6e0e9-675c-4b01-952a-42d68273fc79",{"type":13,"content":14},"doc",[15,21,26,30,37,65,69,74,78,83,87,91,95,100,110],{"type":16,"content":17},"paragraph",[18],{"text":19,"type":20},"A data patch removed nine customer attributes. The patch's own DocBlock said the setup helper \"cascades the stored values\", which sounded right, because every EAV value table does declare the constraint:","text",{"type":22,"content":23},"code_block",[24],{"text":25,"type":20},"FOREIGN KEY (attribute_id) REFERENCES eav_attribute (attribute_id)\n  ON DELETE CASCADE",{"type":16,"content":27},[28],{"text":29,"type":20},"After it ran in production there were 922,986 orphaned rows across three tables.",{"type":31,"attrs":32,"content":34},"heading",{"level":33},2,[35],{"text":36,"type":20},"Why the constraint did nothing",{"type":16,"content":38},[39,41,46,48,51,53,57,59,63],{"text":40,"type":20},"The patch runs inside ",{"text":42,"type":20,"marks":43},"setup:upgrade",[44],{"type":45},"code",{"text":47,"type":20},", and ",{"text":42,"type":20,"marks":49},[50],{"type":45},{"text":52,"type":20}," disables ",{"text":54,"type":20,"marks":55},"FOREIGN_KEY_CHECKS",[56],{"type":45},{"text":58,"type":20}," for the duration. With checks off, the cascade is not enforced. The parent rows in ",{"text":60,"type":20,"marks":61},"eav_attribute",[62],{"type":45},{"text":64,"type":20}," were deleted, the value rows were not, and MySQL raised nothing because it had been told not to look.",{"type":16,"content":66},[67],{"text":68,"type":20},"The constraints were present and unchanged the entire time. They were simply not in force at the one moment they mattered.",{"type":31,"attrs":70,"content":71},{"level":33},[72],{"text":73,"type":20},"The shape of the damage",{"type":16,"content":75},[76],{"text":77,"type":20},"It was not spread evenly. The entity-id side was clean, product value tables were clean, address value tables were clean. Only the attribute side of the customer value tables was affected, and in one of them well over half the rows were orphans. That lopsidedness is a clue in itself: this is specifically what attribute removal leaves behind, not general database rot.",{"type":31,"attrs":79,"content":80},{"level":33},[81],{"text":82,"type":20},"Count them, do not reason about them",{"type":16,"content":84},[85],{"text":86,"type":20},"The lesson I would want someone to take from this is narrow. Do not argue from the presence of a constraint. Go and count:",{"type":22,"content":88},[89],{"text":90,"type":20},"SELECT COUNT(*) FROM customer_entity_int v\nWHERE NOT EXISTS (\n  SELECT 1 FROM eav_attribute a\n  WHERE a.attribute_id = v.attribute_id\n);",{"type":16,"content":92},[93],{"text":94,"type":20},"Run it per value table. It takes a minute and it either returns zero or it does not.",{"type":31,"attrs":96,"content":97},{"level":33},[98],{"text":99,"type":20},"And the ticket is not done when the patch runs",{"type":16,"content":101},[102,104,108],{"text":103,"type":20},"An attribute removal needs the value cleanup written into the release instructions as well, plus an ",{"text":105,"type":20,"marks":106},"OPTIMIZE TABLE",[107],{"type":45},{"text":109,"type":20}," afterwards. Deleting the rows logically does not give the space back: InnoDB reported only a few megabytes of free space per table even after nearly a million rows were gone.",{"type":16,"content":111},[112],{"text":113,"type":20},"None of this is exotic. It is just that \"there is a foreign key\" feels like an answer, and here it was not one.","The constraint says ON DELETE CASCADE. Removing nine attributes still left nearly a million orphaned rows, because setup:upgrade turns foreign key checks off.","blog_post","2026-05-27 09:00","your-foreign-keys-are-not-protecting-your-eav-data","blog\u002Fyour-foreign-keys-are-not-protecting-your-eav-data",null,-40,[],false,215738016545577,"5979825c-a234-4724-a788-921f11ccdcda","default",[]]