13

假设我有两个下表:

CREATE TABLE post (
  id bigint(20)     NOT NULL    AUTO_INCREMENT,
  text text ,

  PRIMARY KEY (id)
) ENGINE=InnoDB AUTO_INCREMENT=1;

CREATE TABLE post_path (
  ancestorid bigint(20)     NOT NULL    DEFAULT '0',
  descendantid bigint(20)   NOT NULL    DEFAULT '0',
  length int(11)            NOT NULL    DEFAULT '0',

  PRIMARY KEY (ancestorid,descendantid),
  KEY descendantid (descendantid),

  CONSTRAINT f_post_path_ibfk_1 
    FOREIGN KEY (ancestorid) REFERENCES post (id) 
    ON DELETE CASCADE 
    ON UPDATE CASCADE,
  CONSTRAINT f_post_path_ibfk_2 
    FOREIGN KEY (descendantid) REFERENCES post (id) 
    ON DELETE CASCADE 
    ON UPDATE CASCADE
) ENGINE=InnoDB;

并插入这些行:

INSERT INTO 
    post (text)
    VALUES ('a'); #// inserted row by id=1
INSERT INTO 
    post_path (ancestorid ,descendantid ,length) 
    VALUES (1, 1, 0);

当我想更新帖子行ID时:

 UPDATE post SET id = '10' WHERE post.id =1

MySQL 说:

#1452 - Cannot add or update a child row: a foreign key constraint fails (test.post_path, CONSTRAINT f_post_path_ibfk_2 FOREIGN KEY (descendantid) REFERENCES post (id) ON DELETE CASCADE ON UPDATE CASCADE) 

为什么?怎么了?

编辑:

当我插入这些行时:

INSERT INTO 
    post (text)
    VALUES ('b'); #// inserted row by id=2

INSERT INTO 
    post_path (ancestorid, descendantid, length)
    VALUES (1, 2, 0);

并更新:

UPDATE post SET id = '20' WHERE post.id =2

Mysql 成功更新了子行和父行。那么为什么我不能更新第一篇文章(id=1)?

4

4 回答 4

6

好的,我通过我也可以访问的测试数据库运行您的架构和查询,并注意到以下内容;在将两行都插入到两个表之后,并且在任何更新之前,数据如下所示:

mysql> select * from post;
+----+------+
| id | text |
+----+------+
|  1 | a    |
|  2 | b    |
+----+------+
2 rows in set (0.00 sec)

mysql> select * from post_path;
+------------+--------------+--------+
| ancestorid | descendantid | length |
+------------+--------------+--------+
|          1 |            1 |      0 |
|          1 |            2 |      0 |
+------------+--------------+--------+
2 rows in set (0.00 sec)

在我发出更新语句后,将 post.id 更新为 20:

mysql> UPDATE `post` SET `id` = '20' WHERE `post`.`id` =2;
Query OK, 1 row affected (0.08 sec)
Rows matched: 1  Changed: 1  Warnings: 0

mysql> select * from post_path;
+------------+--------------+--------+
| ancestorid | descendantid | length |
+------------+--------------+--------+
|          1 |            1 |      0 |
|          1 |           20 |      0 |
+------------+--------------+--------+
2 rows in set (0.00 sec)

请注意,祖先 ID 仍然是 1,这似乎是MySQL的问题:

如果您使用涉及具有外键约束的 InnoDB 表的多表 UPDATE 语句,则 MySQL 优化器可能会以不同于其父/子关系的顺序处理表。在这种情况下,语句失败并回滚。相反,更新单个表并依赖 InnoDB 提供的 ON UPDATE 功能来相应地修改其他表。请参阅第 14.3.5.4 节,“InnoDB 和外键约束”。

您的第一个查询失败的原因是因为祖先 ID 没有更新到 10,但后代是,并且因为您试图将 post.id 设置为 10,并且 post_path 表中的祖先 ID 仍然引用值 1,这不会不再存在。

您应该考虑更改架构以避免这种情况,并避免更新 auto_increment 列以避免冲突。

于 2013-04-12T20:21:54.140 回答
1

我相信您的问题的解决方案是删除descendantid作为约束并使用触发器在字段上执行更新。

delimiter $$
CREATE TRIGGER post_trigger
AFTER UPDATE ON post
FOR EACH ROW
BEGIN
UPDATE post_path SET post_path.descendantid = NEW.id WHERE post_path.descendantid = OLD.id
END$$
于 2013-05-07T02:15:48.933 回答
0

第一次更新失败而第二次更新失败的原因是因为在第二个实例中,您的祖先和后代引用了 post 表中的不同行,

ancestorid   = 1
descendantid = 2

第一次更新在尝试更新 post_path.ancestorid 时失败,因为这样做时 post.id 和 post_path.descendantid 之间的约束将失败,因为这些值将不再匹配 (1 !== 10)。

假设任何给定的帖子不能既是祖先又是后代,那么这里的问题仅在于执行第一个插入:

INSERT INTO `post_path` (`ancestorid` ,`descendantid` ,`length`) VALUES (1, 1, 0);
于 2013-05-07T12:36:00.330 回答
0

第二个起作用的主要原因是您为ancestorid和保留了不同的值descendantid。当您根据特定属性的更改进行两个不同的约束时。只有第一个约束有效,第二个无效。在您的第一次更新尝试中就是这种情况。

于 2013-05-04T17:01:24.997 回答