MySQL ON UPDATE CASCADE不是CASCADEing

aho*_*hoo 13 mysql cascade foreign-keys

假设我有两个下表:

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;
Run Code Online (Sandbox Code Playgroud)

并插入这些行:

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

当我想更新帖子行ID时:

 UPDATE post SET id = '10' WHERE post.id =1
Run Code Online (Sandbox Code Playgroud)

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) 
Run Code Online (Sandbox Code Playgroud)

为什么?怎么了?

编辑:

当我插入这些行:

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

INSERT INTO 
    post_path (ancestorid, descendantid, length)
    VALUES (1, 2, 0);
Run Code Online (Sandbox Code Playgroud)

并更新:

UPDATE post SET id = '20' WHERE post.id =2
Run Code Online (Sandbox Code Playgroud)

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

Mik*_*ell 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)
Run Code Online (Sandbox Code Playgroud)

发出更新语句后,将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)
Run Code Online (Sandbox Code Playgroud)

请注意,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列以避免冲突.