2013-04-12 20 views
11

Załóżmy, że mam dwie poniższa tabela:MySQL ON UPDATE CASCADE nie CASCADEing

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; 

i wstawia te wiersze:

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

Kiedy chcę zaktualizować po rząd ID:

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

MySQL powiedział:

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

Dlaczego? co jest nie tak?

Edit:

Po włożeniu tych rzędów:

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

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

I aktualizacja:

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

Mysql zaktualizowaną powodzeniem zarówno dziecko i rodzic rząd. więc dlaczego nie mogę uaktualnić pierwszy post (id = 1)?

+0

odpowiedź jest w błędzie. –

+0

Instrukcja aktualizacji próbuje zaktualizować kolumnę auto_increment, której nie można wykonać. –

+0

Hmmm nie wiedział, że możesz ręcznie zaktualizować kolumnę auto_increment. Nawet jeśli możesz, nie powinieneś. –

Odpowiedz

5

Ok, wpadłem schematu i zapytań przez testowej bazy danych mam dostęp zbyt i zauważyłem następujące; po włożeniu obu wierszy do obu tabel, a przed wszelkich aktualizacji danych wygląda następująco:

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) 

Po wydać instrukcję aktualizacji, zaktualizować post.id do 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) 

zauważyć, że ancestorid nadal jest 1, to wydaje się być problem z MySQL:

Jeśli użyć instrukcji wielokrotnego tabeli UPDATE udziałem InnoDB tabele, dla których nie są obce kluczowe ograniczenia, optymalizator mi MySQL ght tabele procesów w kolejności, która różni się od ich relacji rodzic/dziecko. W takim przypadku instrukcja nie powiedzie się i wycofa. Zamiast aktualizować pojedynczą tabelę i polegać na możliwości aktualizacji na które InnoDB zapewnia powodować inne tabele zostać odpowiednio zmienione. Zobacz rozdział 14.3.5.4, "InnoDB i FOREIGN KEY Constraints".

Powodem Twoja pierwsza kwerenda się niepowodzeniem, dlatego ancestorid nie aktualizuje do 10, ale descendantid jest i dlatego staramy się ustawić post.id do 10, a ancestorid w tabeli post_path wciąż odwołuje się do wartość 1, która już nie istnieje.

Należy rozważyć zmianę schematu tego uniknąć, a także uniknąć aktualizowanie kolumny auto_increment więc uniknąć kolizji.

+0

, gdy zaktualizuję do 10, zarówno ancestorid, jak i descendantid muszą zostać zmienione w tym samym czasie. powód, dla którego powiedziałeś, nie jest rozsądny. – ahoo

+0

To, co mówię, to, że MySQL nie jest w stanie zaktualizować obu w tym samym czasie. W moich testowych przypadkach nie byłem w stanie zmusić obu do zmiany. –

+0

Rozgryzłeś to? –

0

Głównym powodem, dla którego działało drugie, jest zachowanie różnych wartości dla ancestorid i descendantid. Kiedy robisz dwa różne ograniczenia na podstawie zmiany określonych atrybutów. działa tylko pierwsze ograniczenie, a nie drugie. Tak jest w przypadku pierwszej próby aktualizacji.

+1

Mysql musi spełnić oba ograniczenia jednocześnie, Nie szeregowo jeden, a następnie drugi. ponieważ oba ograniczenia są podstawą tylko jednego atrybutu. czy to błąd? – ahoo

1

Uważam, że rozwiązaniem problemu jest usunięcie descendantid jako ograniczenia i użycie wyzwalacza do przeprowadzenia aktualizacji na tym polu.

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$$ 
0

Powodem pierwsza aktualizacja nie powiedzie się, a drugi nie dlatego, że w drugiej instancji twoi ancestorid i referencyjne descendantid różne wiersze w tabeli Post,

ancestorid = 1 
descendantid = 2 

Pierwsza aktualizacja nie powiedzie się, gdy próbuje zaktualizuj post_path.ancestorid, tak jak robi to ograniczenie między post.id a post_path.descendantid, ponieważ te wartości nie będą już zgodne (1! == 10).

Zakładając, że każdy dany post nie może być zarówno przodek i potomek to problem tutaj jest tylko w wykonaniu pierwszej wkładki:

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

Mysql musi spełnić oba ograniczenia jednocześnie, nie szeregowo jeden, a potem drugi. ponieważ oba ograniczenia są podstawą tylko jednego atrybutu. – ahoo