MySQL外键约束,级联删除
Posted
技术标签:
【中文标题】MySQL外键约束,级联删除【英文标题】:MySQL foreign key constraints, cascade delete 【发布时间】:2011-02-24 06:56:21 【问题描述】:我想使用外键来保持完整性并避免孤儿(我已经使用了 innoDB)。
如何创建一个在 CASCADE 上删除的 SQL 语句?
如果我删除一个类别,那么如何确保它不会删除也与其他类别相关的产品。
数据透视表“categories_products”在其他两个表之间创建了多对多关系。
categories
- id (INT)
- name (VARCHAR 255)
products
- id
- name
- price
categories_products
- categories_id
- products_id
【问题讨论】:
嗨 - 您可能想修改问题标题,它实际上是关于级联删除,而不是专门针对数据透视表。 【参考方案1】:我认为(我不确定)考虑到您的表设计,外键约束不会完全按照您的要求进行。也许最好的办法是定义一个存储过程,该过程将按照您想要的方式删除一个类别,然后在您想要删除一个类别时调用该过程。
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
表)。
【讨论】:
我想我的问题写错了。如果我删除一个类别,那么如何确保它不会删除也与其他类别相关的产品。 好吧,在这种情况下,我认为 Marc B 的回答符合您的要求。 你好@Hammerite,你能告诉我在接受的答案中第三个CREATE TABLE
查询中KEY pkey (product_id),
是什么意思吗?【参考方案2】:
如果您的级联删除了一个产品,因为它是被杀死的类别的成员,那么您的外键设置不正确。鉴于您的示例表,您应该具有以下表设置:
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;
这样,您可以删除一个产品或一个类别,并且只有 categories_products 中的关联记录会随之消失。级联不会在树上走得更远,也不会删除父产品/类别表。
例如
products: boots, mittens, hats, coats
categories: red, green, blue, white, black
prod/cats: red boots, green mittens, red coats, black hats
如果您删除“红色”类别,那么只有类别表中的“红色”条目以及两个条目 prod/cats:“红色靴子”和“红色外套”都会消失。
删除不会进一步级联,也不会删除“靴子”和“外套”类别。
评论跟进:
您仍然误解级联删除的工作原理。它们只影响定义了“on delete cascade”的表。在这种情况下,级联设置在“categories_products”表中。如果您删除“红色”类别,将在 categories_products 中级联删除的唯一记录是 category_id = red
中的记录。它不会触及 'category_id = blue' 的任何记录,也不会继续前进到“products”表,因为该表中没有定义外键。
这是一个更具体的例子:
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会查看所有外键指向'categories'表的表,并删除匹配id为2的记录。由于我们只在products_categories
中定义了外键关系,所以你结束删除完成后更新此表:
+------------+-------------+
| product_id | category_id |
+------------+-------------+
| 1 | 1 | // red mittens
| 2 | 1 | // red boots
+------------+-------------+
products
表中没有定义外键,因此级联将无法在那里工作,因此您仍然列出了靴子和连指手套。不再有“蓝色靴子”和“蓝色手套”了。
【讨论】:
我想我的问题写错了。如果我删除一个类别,那么如何确保它不会删除也与其他类别相关的产品。 这是一个非常棒、非常清晰且插图精美的答案。感谢您花时间写出来。 创建表时,您需要指定 InnoDB 或其他能够进行CASCADE
操作的 mysql 引擎。否则,将使用 MySQL 默认值 MyISAM,并且 MyISAM 不支持 CASCADE
操作。为此,只需在最后一个 ;
之前添加 ENGINE InnoDB
。【参考方案3】:
我对这个问题的答案感到困惑,所以我在 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
【讨论】:
以上是关于MySQL外键约束,级联删除的主要内容,如果未能解决你的问题,请参考以下文章