外键引用两个 child table 到 parent table & grandchild table 到两个 child tables,更新parent table 外键约束失败

Foreign keys reference two child tables to parent table & grandchild table to the two child tables, updating parent table foreign key constraint fails

我创建了 4 个 tables - p_tablec_tablec2_tablecc_table。 table结构如下:-

SHOW CREATE TABLE p_table;
| p_table | CREATE TABLE `p_table` (
  `p_table_id` int unsigned NOT NULL AUTO_INCREMENT,
  `value` varchar(40) DEFAULT NULL,
  PRIMARY KEY (`p_table_id`)
) ENGINE=InnoDB AUTO_INCREMENT=5556 DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_0900_ai_ci |
SHOW CREATE TABLE c_table;
| c_table | CREATE TABLE `c_table` (
  `c_table_id` int unsigned NOT NULL AUTO_INCREMENT,
  `p_table_id` int unsigned DEFAULT NULL,
  `value` varchar(40) DEFAULT NULL,
  PRIMARY KEY (`c_table_id`),
  KEY `i_c_table` (`p_table_id`),
  CONSTRAINT `fk_p_table` FOREIGN KEY (`p_table_id`) REFERENCES `p_table` (`p_table_id`) ON DELETE RESTRICT ON UPDATE CASCADE
) ENGINE=InnoDB AUTO_INCREMENT=556 DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_0900_ai_ci |
SHOW CREATE TABLE c2_table;
| c2_table | CREATE TABLE `c2_table` (
  `c2_table_id` int unsigned NOT NULL AUTO_INCREMENT,
  `p_table_id` int unsigned DEFAULT NULL,
  `value` varchar(40) DEFAULT NULL,
  PRIMARY KEY (`c2_table_id`),
  KEY `i_c2_table` (`p_table_id`),
  CONSTRAINT `fk2_p_table` FOREIGN KEY (`p_table_id`) REFERENCES `p_table` (`p_table_id`) ON DELETE RESTRICT ON UPDATE CASCADE
) ENGINE=InnoDB AUTO_INCREMENT=16 DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_0900_ai_ci |
SHOW CREATE TABLE cc_table;
| cc_table | CREATE TABLE `cc_table` (
  `cc_table_id` int unsigned NOT NULL AUTO_INCREMENT,
  `p_table_id` int unsigned DEFAULT NULL,
  `value` varchar(40) DEFAULT NULL,
  PRIMARY KEY (`cc_table_id`),
  KEY `i_cc_table` (`p_table_id`),
  CONSTRAINT `fk_c2_table` FOREIGN KEY (`p_table_id`) REFERENCES `c2_table` (`p_table_id`) ON DELETE RESTRICT ON UPDATE CASCADE,
  CONSTRAINT `fk_c_table` FOREIGN KEY (`p_table_id`) REFERENCES `c_table` (`p_table_id`) ON DELETE RESTRICT ON UPDATE CASCADE
) ENGINE=InnoDB AUTO_INCREMENT=16 DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_0900_ai_ci |

此处,table c_table 的第 p_table_id 列和 table c2_table 的第 p_table_id 列引用了 p_table_id 列table p_table 通过外键。

同样,table cc_table 的第 p_table_id 列引用了 table c_table 的第 p_table_id 列和 p_table_id 列table c2_table 通过外键。

所有参考中的参考选项都是-

ON DELETE RESTRICT ON UPDATE CASCADE

这是某种 1-2-1 连锁结构。

现在,我按如下方式填充 4 tables :-

SELECT * FROM p_table;
+------------+----------+
| p_table_id | value    |
+------------+----------+
|          1 | p_value1 |
|          2 | p_value2 |
|          3 | p_value3 |
|          4 | p_value4 |
|          5 | p_value5 |
+------------+----------+
SELECT * FROM c_table;
+------------+------------+-----------+
| c_table_id | p_table_id | value     |
+------------+------------+-----------+
|          1 |          1 | c_value11 |
|          2 |          2 | c_value21 |
|          3 |          2 | c_value22 |
|          4 |          3 | c_value31 |
|          5 |          3 | c_value32 |
|          6 |          3 | c_value33 |
|          7 |          4 | c_value41 |
|          8 |          4 | c_value42 |
|          9 |          4 | c_value43 |
|         10 |          4 | c_value44 |
|         11 |          5 | c_value51 |
|         12 |          5 | c_value52 |
|         13 |          5 | c_value53 |
|         14 |          5 | c_value54 |
|         15 |          5 | c_value55 |
+------------+------------+-----------+
SELECT * FROM c2_table;
+-------------+------------+-----------+
| c2_table_id | p_table_id | value     |
+-------------+------------+-----------+
|           1 |          1 | c_value11 |
|           2 |          2 | c_value21 |
|           3 |          2 | c_value22 |
|           4 |          3 | c_value31 |
|           5 |          3 | c_value32 |
|           6 |          3 | c_value33 |
|           7 |          4 | c_value41 |
|           8 |          4 | c_value42 |
|           9 |          4 | c_value43 |
|          10 |          4 | c_value44 |
|          11 |          5 | c_value51 |
|          12 |          5 | c_value52 |
|          13 |          5 | c_value53 |
|          14 |          5 | c_value54 |
|          15 |          5 | c_value55 |
+-------------+------------+-----------+
SELECT * FROM cc_table;
+-------------+------------+------------+
| cc_table_id | p_table_id | value      |
+-------------+------------+------------+
|           1 |          1 | cc_value11 |
|           2 |          1 | cc_value12 |
|           3 |          1 | cc_value13 |
|           4 |          1 | cc_value14 |
|           5 |          1 | cc_value15 |
|           6 |          2 | cc_value21 |
|           7 |          2 | cc_value22 |
|           8 |          2 | cc_value23 |
|           9 |          2 | cc_value24 |
|          10 |          3 | cc_value31 |
|          11 |          3 | cc_value32 |
|          12 |          3 | cc_value33 |
|          13 |          4 | cc_value41 |
|          14 |          4 | cc_value42 |
|          15 |          5 | cc_value51 |
+-------------+------------+------------+

现在,我尝试更新 table p_table 中的一个值。预期的行为是所有 child table 的相应值将得到更新。

但是,我收到以下错误 -

UPDATE p_table
    -> SET p_table_id = 3333 WHERE p_table_id = 3;
ERROR 1452 (23000): Cannot add or update a child row: a foreign key constraint fails (`constraints`.`cc_table`, CONSTRAINT `fk_c_table` FOREIGN KEY (`p_table_id`) REFERENCES `c_table` (`p_table_id`) ON DELETE RESTRICT ON UPDATE CASCADE)

现在,我尝试删除链接 cc_tablec2_table 的外键 fk_c2_table。所有 child table 最终都链接到 parent table p_table.

ALTER TABLE cc_table
    -> DROP FOREIGN KEY fk_c2_table;

现在,我尝试更新 table p_table 中的一个值,我成功了。

UPDATE p_table
    -> SET p_table_id = 3333 WHERE p_table_id = 3;
Query OK, 1 row affected (0.06 sec)
Rows matched: 1  Changed: 1  Warnings: 0

三个 child table 中的相应值 - c_tablec2_tablec2_table 也按预期更新。

因此,这里的要点是类型为 1-1-1 的链式结构有效,但类型为 - 1-2-1 的链式结构无效。为什么会这样?有什么方法可以让它发挥作用吗?


经过进一步调查,如果将 child table 链接到两个 parent table 是否有效,我删除了外键 - fk_p_table , fk2_p_table.

ALTER TABLE c_table
    -> DROP FOREIGN KEY fk_p_table;
ALTER TABLE c2_table 
    -> DROP FOREIGN KEY fk2_p_table;

另外我 re-add 外键 - fk_c2_table.

ALTER TABLE cc_table
    -> ADD CONSTRAINT fk_c2_table FOREIGN KEY (p_table_id)
    -> REFERENCES c2_table (p_table_id)
    -> ON DELETE RESTRICT
    -> ON UPDATE CASCADE;

c_tablec2_tablecc_table的table结构现在如下:-

SHOW CREATE TABLE c_table;
| c_table | CREATE TABLE `c_table` (
  `c_table_id` int unsigned NOT NULL AUTO_INCREMENT,
  `p_table_id` int unsigned DEFAULT NULL,
  `value` varchar(40) DEFAULT NULL,
  PRIMARY KEY (`c_table_id`),
  KEY `i_c_table` (`p_table_id`)
) ENGINE=InnoDB AUTO_INCREMENT=556 DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_0900_ai_ci |
SHOW CREATE TABLE c2_table;
| c2_table | CREATE TABLE `c2_table` (
  `c2_table_id` int unsigned NOT NULL AUTO_INCREMENT,
  `p_table_id` int unsigned DEFAULT NULL,
  `value` varchar(40) DEFAULT NULL,
  PRIMARY KEY (`c2_table_id`),
  KEY `i_c2_table` (`p_table_id`)
) ENGINE=InnoDB AUTO_INCREMENT=16 DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_0900_ai_ci |
SHOW CREATE TABLE cc_table;
| cc_table | CREATE TABLE `cc_table` (
  `cc_table_id` int unsigned NOT NULL AUTO_INCREMENT,
  `p_table_id` int unsigned DEFAULT NULL,
  `value` varchar(40) DEFAULT NULL,
  PRIMARY KEY (`cc_table_id`),
  KEY `i_cc_table` (`p_table_id`),
  CONSTRAINT `fk_c2_table` FOREIGN KEY (`p_table_id`) REFERENCES `c2_table` (`p_table_id`) ON DELETE RESTRICT ON UPDATE CASCADE,
  CONSTRAINT `fk_c_table` FOREIGN KEY (`p_table_id`) REFERENCES `c_table` (`p_table_id`) ON DELETE RESTRICT ON UPDATE CASCADE
) ENGINE=InnoDB AUTO_INCREMENT=16 DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_0900_ai_ci |

所以,现在这基本上是一个1-2连锁阵型。

现在,我尝试更新 table c_table 和 table c2_table 中的值,我成功了 -

UPDATE c_table
    -> SET p_table_id = 2 WHERE p_table_id = 1;
UPDATE c2_table
    -> SET p_table_id = 4 WHERE p_table_id = 5;

child table cc_table 中的相应值也按预期更新。

所以,要点是 2-1 和 1-2 连锁阵型都有效,但 1-2-1 连锁阵型无效。

为什么 1-2-1 连锁阵型不起作用?有什么方法可以让我完成这项工作吗?

抓得好!

这似乎是 MySQL 8.x 中的错误。我按照您指定的方式尝试了一个包含所有 4 个 FK 的简化示例,并且能够重现该错误。我会向 MySQL 团队提交错误。这没什么大不了的,因为 PKs/FKs 很少改变(尽管他们可以),而且你的例子是一个很大的延伸(尽管它应该有效)。

我在 PostgreSQL 13 中尝试了相同的脚本,它非常有效。见下文。

MySQL 8 个示例 -- 不起作用

DB Fiddle - MySQL 8:

create table t (a int primary key not null);

create table u (
  a int primary key not null,
  constraint fk1 foreign key (a) references t (a) on update cascade
);

create table v (
  a int primary key not null,
  constraint fk2 foreign key (a) references t (a) on update cascade
);

create table w (
  a int,
  constraint fk3 foreign key (a) references u (a) on update cascade,
  constraint fk4 foreign key (a) references v (a) on update cascade
);

insert into t (a) values (123);
insert into t (a) values (456);
insert into u (a) values (123);
insert into u (a) values (456);
insert into v (a) values (123);
insert into w (a) values (123);

update t set a = 789 where a = 123;

产生错误:

ER_NO_REFERENCED_ROW_2: Cannot add or update a child row: a foreign key constraint fails (test.w, CONSTRAINT fk4 FOREIGN KEY (a) REFERENCES v (a) ON UPDATE CASCADE)

PostgreSQL 13 示例 -- 有效

参见DB Fiddle - PostgreSQL 13

create table t (a int primary key not null);

create table u (
  a int primary key not null,
  constraint fk1 foreign key (a) references t (a) on update cascade
);

create table v (
  a int primary key not null,
  constraint fk2 foreign key (a) references t (a) on update cascade
);

create table w (
  a int,
  constraint fk3 foreign key (a) references u (a) on update cascade,
  constraint fk4 foreign key (a) references v (a) on update cascade
);

insert into t (a) values (123);
insert into t (a) values (456);
insert into u (a) values (123);
insert into u (a) values (456);
insert into v (a) values (123);
insert into w (a) values (123);

update t set a = 789 where a = 123;

所有四个表都按预期更新,如您在 fiddle.

中所见