添加行后,如何检查给定行中的参照完整性?

时间:2011-06-24 15:51:59

标签: mysql innodb referential-integrity

我正在尝试查看在将foreign_key_checks设置为0的同时将单行的参照完整性插入到InnoDB表中后是否有办法检查单个行的参照完整性。

所以给出了两个表:

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 会失败。

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

任何逃避我的见解或其他方法都将受到赞赏。

(如果你好奇为什么我正在尝试做所有这些,那是因为我正在尝试帮助解决Django框架中的open issue,其中带有前向引用的灯具无法加载。建议的解决方案是在加载灯具时禁用外键检查,并在加载完成后重新启用它们。我试图找出在那时如何返回并检查行的参照完整性在外键检查关闭时添加,如果出现问题则引发错误。)

2 个答案:

答案 0 :(得分:6)

我提出了一个解决方案,我实际上要感谢这个问题的答案: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中触发错误 - 出于我的目的,我可以在应用程序代码中引发错误 - 所以这就是我需要去的地方。但这最终成为一个相当简单快速的解决方案,所以我很高兴我找到了它!

答案 1 :(得分: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;

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