MySQL ON UPDATE CASCADE不是CASCADing

时间:2013-04-12 17:45:25

标签: mysql foreign-keys cascade

假设我有两张下表:

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 个答案:

答案 0 :(得分:5)

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

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)

请注意,ancestorid仍为1,这似乎是MySQL的问题:

  

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

你的第一个查询失败的原因是因为ancestorid没有更新到10,但是descendantid是,并且因为你试图将post.id设置为10,而post_path表中的ancestorid仍然引用值1,这将不复存在。

您应该考虑更改模式以避免这种情况,并且还要避免更新auto_increment列以避免冲突。

答案 1 :(得分: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$$

答案 2 :(得分:0)

第二个工作的主要原因是您为ancestoriddescendantid保留了不同的值。当您根据特定属性的更改制作两个不同的约束时。只有第一个约束才有效,而不是第二个约束。在您的第一次更新尝试中就是这种情况。

答案 3 :(得分:0)

第一次更新失败的原因和第二次更新失败的原因是在第二次实例中你的ancestorid和descendantid引用了帖子表中的不同行,

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);