MySQL外键约束,级联删除


158

我想使用外键来保持完整性并避免使用孤立键(我已经使用过innoDB)。

如何创建在CASCADE上删除的SQL语句?

如果我删除类别,那么如何确保它不会删除也与其他类别相关的产品。

数据透视表“ categories_products”在其他两个表之间创建多对多关系。

categories
- id (INT)
- name (VARCHAR 255)

products
- id
- name
- price

categories_products
- categories_id
- products_id

嗨-您可能想要修改问题标题,它实际上是关于级联删除的,而不是专门用于数据透视表的。
Paddyslacker 2010年

Answers:


386

如果您的级联因为某个产品被杀而删除了nuke产品,那么您的外键设置不正确。给定示例表,您应该具有以下表设置:

CREATE TABLE categories (
    id int unsigned not null primary key,
    name VARCHAR(255) default null
)Engine=InnoDB;

CREATE TABLE products (
    id int unsigned not null primary key,
    name VARCHAR(255) default null
)Engine=InnoDB;

CREATE TABLE categories_products (
    category_id int unsigned not null,
    product_id int unsigned not null,
    PRIMARY KEY (category_id, product_id),
    KEY pkey (product_id),
    FOREIGN KEY (category_id) REFERENCES categories (id)
       ON DELETE CASCADE
       ON UPDATE CASCADE,
    FOREIGN KEY (product_id) REFERENCES products (id)
       ON DELETE CASCADE
       ON UPDATE CASCADE
)Engine=InnoDB;

这样,您可以删除产品或类别,只有category_products中的关联记录会一起消失。级联将不会沿着树走得更远,并删除父产品/类别表。

例如

products: boots, mittens, hats, coats
categories: red, green, blue, white, black

prod/cats: red boots, green mittens, red coats, black hats

如果删除“红色”类别,则类别表中的“红色”条目以及两个条目prod / cats:“红色靴子”和“红色外套”都会消失。

删除操作不会进一步进行,也不会删除“靴子”和“外套”类别。

评论跟进:

您仍然误解了级联删除的工作方式。它们仅影响定义了“删除时级联”的表。在这种情况下,级联设置在“ categories_products”表中。如果删除“红色”类别,则将在category_products中级联删除的唯一记录是那些category_id = red。它不会触及“ category_id = blue”的任何记录,也不会继续前进到“产品”表,因为该表中没有定义外键。

这是一个更具体的示例:

categories:     products:
+----+------+   +----+---------+
| id | name |   | id | name    |
+----+------+   +----+---------+
| 1  | red  |   | 1  | mittens |
| 2  | blue |   | 2  | boots   |
+---++------+   +----+---------+

products_categories:
+------------+-------------+
| product_id | category_id |
+------------+-------------+
| 1          | 1           | // red mittens
| 1          | 2           | // blue mittens
| 2          | 1           | // red boots
| 2          | 2           | // blue boots
+------------+-------------+

假设您删除类别2(蓝色):

DELETE FROM categories WHERE (id = 2);

DBMS将查看所有具有指向“类别”表的外键的表,并删除匹配ID为2的记录。由于我们仅在中定义了外键关系products_categories,因此一旦删除完成:

+------------+-------------+
| product_id | category_id |
+------------+-------------+
| 1          | 1           | // red mittens
| 2          | 1           | // red boots
+------------+-------------+

products表中没有定义外键,因此级联在那里将不起作用,因此您仍然列出了靴子和连指手套。不再有“蓝色靴子”和“蓝色手套”了。


我想我以错误的方式写了我的问题。如果我删除一个类别,那么如何确保它不会删除也与其他类别相关的产品。
Cudos

36
这是一个非常棒的,高度醒目的且插图精美的答案。感谢您抽出宝贵的时间将其全部写下。
斯科特,

2
创建表时,您需要指定InnoDB或其他可以CASCADE运行的MySQL引擎。否则,将使用MySQL默认值MyISAM,而MyISAM不支持CASCADE操作。为此,只需ENGINE InnoDB在last之前添加;
Patrick

11

我对这个问题的答案感到困惑,所以我在MySQL中创建了一个测试用例,希望这会有所帮助

-- Schema
CREATE TABLE T1 (
    `ID` int not null auto_increment,
    `Label` varchar(50),
    primary key (`ID`)
);

CREATE TABLE T2 (
    `ID` int not null auto_increment,
    `Label` varchar(50),
    primary key (`ID`)
);

CREATE TABLE TT (
    `IDT1` int not null,
    `IDT2` int not null,
    primary key (`IDT1`,`IDT2`)
);

ALTER TABLE `TT`
    ADD CONSTRAINT `fk_tt_t1` FOREIGN KEY (`IDT1`) REFERENCES `T1`(`ID`) ON DELETE CASCADE,
    ADD CONSTRAINT `fk_tt_t2` FOREIGN KEY (`IDT2`) REFERENCES `T2`(`ID`) ON DELETE CASCADE;

-- Data
INSERT INTO `T1` (`Label`) VALUES ('T1V1'),('T1V2'),('T1V3'),('T1V4');
INSERT INTO `T2` (`Label`) VALUES ('T2V1'),('T2V2'),('T2V3'),('T2V4');
INSERT INTO `TT` (`IDT1`,`IDT2`) VALUES
(1,1),(1,2),(1,3),(1,4),
(2,1),(2,2),(2,3),(2,4),
(3,1),(3,2),(3,3),(3,4),
(4,1),(4,2),(4,3),(4,4);

-- Delete
DELETE FROM `T2` WHERE `ID`=4; -- Delete one field, all the associated fields on tt, will be deleted, no change in T1
TRUNCATE `T2`; -- Can't truncate a table with a referenced field
DELETE FROM `T2`; -- This will do the job, delete all fields from T2, and all associations from TT, no change in T1

8

我认为(不确定)外键约束不能完全按照您的表设计来做。也许最好的办法是定义一个存储过程,该存储过程将以您想要的方式删除类别,然后在您要删除类别时调用该过程。

CREATE PROCEDURE `DeleteCategory` (IN category_ID INT)
LANGUAGE SQL
NOT DETERMINISTIC
MODIFIES SQL DATA
SQL SECURITY DEFINER
BEGIN

DELETE FROM
    `products`
WHERE
    `id` IN (
        SELECT `products_id`
        FROM `categories_products`
        WHERE `categories_id` = category_ID
    )
;

DELETE FROM `categories`
WHERE `id` = category_ID;

END

您还需要向链接表添加以下外键约束:

ALTER TABLE `categories_products` ADD
    CONSTRAINT `Constr_categoriesproducts_categories_fk`
    FOREIGN KEY `categories_fk` (`categories_id`) REFERENCES `categories` (`id`)
    ON DELETE CASCADE ON UPDATE CASCADE,
    CONSTRAINT `Constr_categoriesproducts_products_fk`
    FOREIGN KEY `products_fk` (`products_id`) REFERENCES `products` (`id`)
    ON DELETE CASCADE ON UPDATE CASCADE

当然,CONSTRAINT子句也可以出现在CREATE TABLE语句中。

创建了这些架构对象之后,您可以删除类别并通过发出来获得所需的行为CALL DeleteCategory(category_ID)(其中category_ID是要删除的类别),并且它将按照您想要的方式运行。但是,不要发出普通DELETE FROM查询,除非您想要更多标准行为(即,仅从链接表中删除,并保留该products表)。


我想我以错误的方式写了我的问题。如果我删除一个类别,那么如何确保它不会删除也与其他类别相关的产品。
Cudos

好的,在那种情况下,我认为Marc B的答案可以满足您的要求。
Hammerite 2010年

您好@Hammerite,能否请您告诉我接受的答案中KEY pkey (product_id),第三个CREATE TABLE查询的含义是什么?
Siraj Alam
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.