简体   繁体   中英

How can I check for referential integrity in a given row after the row has been added?

I'm trying to see if there is a way to check the referential integrity of a single row after it has been inserted into an InnoDB table while foreign_key_checks is set to 0.

So given two tables:

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;

We run the following statements against them:

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

So we've put some bad data in here -- author_id refers to nonexistent person.id 3. I'm attempting to find a way to trigger a check of some kind on that row which will throw an error if there is a reference problem like this one.

The approach I initially was trying to take was to update the row with the exact same data. I tried this:

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

I expected this would trigger an error -- however, it did not. Since the data did not change, apparently no integrity check is made? Setting author_id to 2 does fail, for example.

My question then: Is there an alternative that would allow me to inspect a given row and trigger an integrity error if there is a problem? I've googled around but I have found nothing. I'd like to avoid checking the whole table but that might be the only route.

Any insights or other approaches that are escaping me would be appreciated.

(If you're curious why I'm trying to do all this, it's because I'm trying to help resolve an open issue in the Django framework where fixtures with forward references cannot be loaded. The proposed solution is to disable foreign key checks when fixtures are loaded and re-enabled them after the load is complete. I'm trying to figure out how at that point to go back and check the referential integrity of the rows that were added while foreign key checks were off and to raise an error if there is a problem.)

I came up with a solution, which I actually got to thanks to the answer in this question: Force InnoDB to recheck foreign keys on a table/tables?

I had seen that question before, but I finally spent about 20 minutes unpacking all the abstractions and trying to understand what it is doing in concrete terms. It's really not all that complicated, actually. Phrasing the core of the solution in terms of the example I gave in this question, you've basically got two SELECT statements:

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

That will return all of the key columns in the book table. We can iterate over these results and check their references as follows -- in this example using "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

Basically, that statement will return any rows where there is a value in author_id but no corresponding value for id in the related table. These are the "bad rows".

I didn't need to trigger the error directly in MySQL -- for my purposes I can raise an error in application code -- so this was as far as I needed to go. But this ended up being a fairly straightforward and fast solution, so I'm glad I found it!

The UPDATE trick didn't work because MySQL ignored the update, as nothing had changed:

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

You can therefore update first to a different value, and then back to the original value, which will then throw the desired error. I've wrapped the whole thing in a transaction so that it can be rolled back without affecting the database:

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;

It seems like a messy and potentially dangerous solution, but I don't know enough about the original problem... I also don't know if you actually want to keep the original value in the table. If you do, you'll have to disable the foreign key constraints again in order to reset it!

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM