4

我正在尝试查看是否有一种方法可以在将单行插入 InnoDB 表foreign_key_checks且设置为 0 后检查其引用完整性。

所以给定两个表:

CREATE TABLE `book` (
  `id` int(11) NOT NULL AUTO_INCREMENT,
  `name` varchar(255) NOT NULL,
  `author_id` int(11) NOT NULL,
  PRIMARY KEY (`id`),
  KEY `book_cc846901` (`author_id`),
  CONSTRAINT `author_id_refs_id_7fdd0933` FOREIGN KEY (`author_id`) REFERENCES `person` (`id`)
) ENGINE=InnoDB;


CREATE TABLE `person` (
  `id` int(11) NOT NULL AUTO_INCREMENT,
  `name` varchar(255) NOT NULL,
  PRIMARY KEY (`id`)
) ENGINE=InnoDB;

我们对它们运行以下语句:

SET foreign_key_checks=0
INSERT INTO `book` (`id`, `name`, `author_id`) VALUES (1, 'Cryptonomicon', 3)
INSERT INTO `person` (`id`, `name`) VALUES (4, 'Neal Stephenson')
SET foreign_key_checks=1

所以我们在这里放了一些不好的数据——author_id 指的是不存在的 person.id 3。我试图找到一种方法来触发对该行的某种检查,如果存在参考问题,这将引发错误像这个。

我最初尝试采用的方法是使用完全相同的数据更新行。我试过这个:

UPDATE `book` SET `name` = 'Cryptonomicon', `author_id` = 3 WHERE `book`.`id` = 1 

我预计这会触发一个错误——但是,它没有。由于数据没有改变,显然没有进行完整性检查?例如,将 author_id 设置为 2确实会失败。

那么我的问题是:是否有替代方法可以让我检查给定的行并在出现问题时触发完整性错误?我已经用谷歌搜索了,但我什么也没找到。我想避免检查整个表格,但这可能是唯一的途径。

任何使我无法理解的见解或其他方法将不胜感激。

(如果你好奇我为什么要这样做,那是因为我试图帮助解决 Django 框架中的一个未解决问题,即无法加载具有前向引用的固定装置。建议的解决方案是禁用外键检查当加载完成后加载并重新启用固定装置时.我试图弄清楚如何返回并检查在外键检查关闭时添加的行的引用完整性并引发有问题就报错。)

4

2 回答 2

8

我想出了一个解决方案,实际上我要感谢这个问题的答案:Force InnoDB to recheck foreign keys on a table/tables?

我之前看到过这个问题,但我最终花了大约 20 分钟来解开所有抽象,并试图具体理解它在做什么。实际上,这并没有那么复杂。根据我在这个问题中给出的例子来表达解决方案的核心,你基本上有两个 SELECT 语句:

SELECT *
FROM information_schema.KEY_COLUMN_USAGE
WHERE
    `CONSTRAINT_SCHEMA` LIKE `test`
    AND `TABLE_NAME` = `book`

这将返回表中的所有键列book。我们可以遍历这些结果并检查它们的引用如下——在这个例子中使用“author_id”:

SELECT * FROM `book` as REFERRING
LEFT JOIN `person` as REFERRED
ON (REFERRING.`author_id` = REFERRED.`id`)
WHERE REFERRING.`author_id` IS NOT NULL
AND REFERRED.`id` IS NULL

基本上,该语句将返回 author_id 中有值但相关表中 id 没有对应值的任何行。这些是“坏行”。

我不需要直接在 MySQL 中触发错误——出于我的目的,我可以在应用程序代码中引发错误——所以这就是我需要做的。但这最终成为一个相当简单和快速的解决方案,所以我很高兴我找到了它!

于 2011-06-24T20:58:02.700 回答
1

UPDATE 技巧不起作用,因为 MySQL 忽略了更新,因为没有任何改变:

UPDATE `book` SET
  `name` = 'Cryptonomicon',
  `author_id` = 3
WHERE `book`.`id` = 1;
Query OK, 0 rows affected (0.03 sec)
Rows matched: 1  Changed: 0  Warnings: 0

因此,您可以先更新为不同的值,然后再更新回原始值,这将引发所需的错误。我已经将整个事情包装在一个事务中,以便可以在不影响数据库的情况下回滚它:

START TRANSACTION;

SET foreign_key_checks=0;

INSERT INTO `book` (`id`, `name`, `author_id`)
VALUES (1, 'Cryptonomicon', 3);

SET foreign_key_checks=1;

INSERT INTO `person` (`name`) VALUES ('NULL');

UPDATE `book` SET
  `author_id` = LAST_INSERT_ID()
WHERE `book`.`id` = 1;

UPDATE `book` SET
  `author_id` = 3
WHERE `book`.`id` = 1;

ERROR 1452 (23000): Cannot add or update a child row: a foreign key constraint fails (`test`.`book`, CONSTRAINT `author_id_refs_id_7fdd0933` FOREIGN KEY (`author_id`) REFERENCES `person` (`id`))

ROLLBACK;

这似乎是一个混乱且具有潜在危险的解决方案,但我对原始问题的了解不够……我也不知道您是否真的想将原始值保留在表中。如果这样做,则必须再次禁用外键约束才能重置它!

于 2011-06-24T20:16:51.490 回答