Enforcing Foreign Keys Programmatically in MySQL
Enforcing Foreign Keys Programmatically in MySQL
In general, a foreign key is a field within a database record that points to a key (or group of fields forming a key) of another
record in a different table. In this arrangement, a foreign key in one table will typically refer to the primary key of another
table. This enables references which can be made to link information together. This type of design is a major component of
what is known as “database normalization”. It should also be noted, that data, which serves as a foreign key in one record,
cannot be removed if there is another record that assumes its existence.
z Assuming the proper design of the relationships, foreign key constraints make it more difficult for a programmer to
introduce an inconsistency into the database.
z Centralizing the checking of these constraints by the database server makes it unnecessary to perform these checks
on the application side. This eliminates the possibility that different applications may not check constraints in the
same way.
z Using cascading updates and deletes can simplify the application code.
z Properly designed foreign key rules aid in documenting relationships between tables.
As we mentioned in the introduction, for storage engines other then InnoDB and the upcoming code-named “Falcon” storage
engine, foreign keys are not natively supported. MySQL in turn, parses the FOREIGN KEY syntax in CREATE TABLE
statements, but does not use or store it. In the absence of server-side foreign key relationship checking, the workaround
described in this article shows you how to handle these relationship issues.
Please note, for technical and performance reasons, the following assumed:
First, we’ll create a table called “error_msg”. This table will hold the error message we’ll send to a user when a foreign key
constraint is “violated”.
Restricting INSERTS
In this example we will use programmatic foreign keys to restrict INSERT operations. Below is the SQL for creating three
parent tables using the InnoDB, NDB and MyISAM storage engines.
Next we’ll create three associated child tables for each storage engine.
Please note that the parent_id in the child tables refers to the parent_id of the parent tables.
The next step involves creating triggers for the NDB and MyISAM child tables which will ensure a corresponding parent_id
value exists in the parent table when we insert a value into the child table.
Now, we’ll demonstrate how the INSERT validation works by first seeding the parent and child tables with data.
ERROR 1452 (23000): Cannot add or update a child row: a foreign key constraint fails
(`fk/innodb_child`, CONSTRAINT `innodb_child_ibfk_1` FOREIGN KEY (`iparent_id`) REFERENCES
`innodb_parent` (`iparen
t_id`))
Let’s try the same example with the NDB storage engine which leverages programmatically enforced foreign keys.
ERROR 1062 (23000): Duplicate entry 'Foreign Key Constraint Violated!' for key 'PRIMARY'
Finally, let’s try this example with the MyISAM storage engine which also leverages programmatically enforced foreign keys.
ERROR 1062 (23000): Duplicate entry 'Foreign Key Constraint Violated!' for key 'PRIMARY'
In all the examples above we have prohibited the insertion of the values into the child tables because of foreign key
violations.
A cascading delete specifies that if an attempt is made to delete a row with a key referenced by foreign keys in existing rows
in other tables, all rows that contain those foreign keys are also deleted.
A cascading update specifies that if an attempt is made to update a key value in a row, where the key value is referenced by
foreign keys in existing rows in other tables, all the values that make up the foreign key are also updated to the new value
specified for the key.
Drop all your previously created objects. Recreate and reseed your base error, parent and child tables. We’ll need to
redefine our InnoDB child table as shown.
Please note the update and delete will be cascaded from parent to child.
To ensure the parent_id in the child table refers to a valid key whenever an UPDATE SET parent_id= is issued, we’ll need to
create the following triggers for the NDB and MyISAM tables.
Next we ensure that referenced rows in the child tables are deleted whenever a corresponding row is deleted from the
parent tables. We accomplish this by creating two additional triggers.
Finally we ensure an update to the referenced key in the child table if there is an update on the parent_id column of the
parent table.
First, let’s test the validity of our updates on the InnoDB child table.
ERROR 1452 (23000): Cannot add or update a child row: a foreign key constraint fails
(`fk/innodb_child`, CONSTRAINT `innodb_child_ibfk_1` FOREIGN KEY (`iparent_id`)
REFERENCES `innodb_parent` (`iparent_id`) ON DELETE CASCADE ON UPDATE CASCADE)
Next let’s do a similar test on the NDB and MyISAM child tables.
ERROR 1062 (23000): Duplicate entry 'Foreign Key Constraint Violated!' for key 'PRIMARY'
ERROR 1062 (23000): Duplicate entry 'Foreign Key Constraint Violated!' for key 'PRIMARY'
+------------+
| iparent_id |
+------------+
| 1 |
| 2 |
| 4 |
+------------+
3 rows in set (0.00 sec)
+------------+-----------+
| iparent_id | ichild_id |
+------------+-----------+
| 1 | 1 |
| 1 | 2 |
| 2 | 1 |
| 2 | 2 |
| 2 | 3 |
| 4 | 1 |
+------------+-----------+
6 rows in set (0.00 sec)
Now let’s do the same test on the NDB and MyISAM tables using triggers to enforce the constraints.
+------------+
| nparent_id |
+------------+
| 1 |
| 2 |
| 4 |
+------------+
3 rows in set (0.00 sec)
+------------+-----------+
| nparent_id | nchild_id |
+------------+-----------+
| 1 | 1 |
| 1 | 2 |
| 2 | 1 |
| 2 | 2 |
| 2 | 3 |
| 4 | 1 |
+------------+-----------+
6 rows in set (0.00 sec)
+------------+
| mparent_id |
+------------+
| 1 |
| 2 |
| 4 |
+------------+
3 rows in set (0.00 sec)
SELECT * FROM myisam_child;
+------------+-----------+
| mparent_id | mchild_id |
+------------+-----------+
| 1 | 1 |
| 1 | 2 |
| 2 | 1 |
| 2 | 2 |
| 2 | 3 |
| 4 | 1 |
+------------+-----------+
6 rows in set (0.01 sec)
Next let’s look at how cascading deletes are handled using the InnoDB tables which natively support this functionality.
+------------+-----------+
| iparent_id | ichild_id |
+------------+-----------+
| 1 | 1 |
| 1 | 2 |
| 2 | 1 |
| 2 | 2 |
| 2 | 3 |
+------------+-----------+
5 rows in set (0.00 sec)
Now let’s do the same test on the NDB and MyISAM tables using triggers to enforce the constraints.
+------------+-----------+
| nparent_id | nchild_id |
+------------+-----------+
| 1 | 1 |
| 1 | 2 |
| 2 | 1 |
| 2 | 2 |
| 2 | 3 |
+------------+-----------+
5 rows in set (0.00 sec)
+------------+-----------+
| mparent_id | mchild_id |
+------------+-----------+
| 1 | 1 |
| 1 | 2 |
| 2 | 1 |
| 2 | 2 |
| 2 | 3 |
+------------+-----------+
5 rows in set (0.00 sec)
Restricting UPDATES and DELETES
Drop all your previously created objects. Recreate and reseed your base error, parent and child tables. We’ll need to
redefine our InnoDB child table as shown.
Updates and deletes will now be forbidden if there is any cross reference.
In order to ensure the parent_id in the child table refers to a valid key when an UPDATE SET parent_id= is issued, we’ll go
ahead and create the following triggers for the NDB and MyISAM tables.
Next we’ll forbid the update of parent_id from the parent table if there are any referenced parent_id rows in the child table by
creating two additional triggers.
Now let’s verify the constraints. First by demonstrating it natively using InnoDB, and then with the use of triggers on the NDB
and MyISAM.
ERROR 1451 (23000): Cannot delete or update a parent row: a foreign key constraint fails
(`fk/innodb_child`, CONSTRAINT `innodb_child_ibfk_1` FOREIGN KEY (`iparent_id`)
REFERENCES `innodb_parent` (`iparent_id`))
ERROR 1062 (23000): Duplicate entry 'Foreign Key Constraint Violated!' for key 'PRIMARY'
ERROR 1062 (23000): Duplicate entry 'Foreign Key Constraint Violated!' for key 'PRIMARY'
ERROR 1451 (23000): Cannot delete or update a parent row: a foreign key constraint fails
(`fk/innodb_child`, CONSTRAINT `innodb_child_ibfk_1` FOREIGN KEY (`iparent_id`) REFERENCES
`innodb_parent` (`iparent_id`))
ERROR 1062 (23000): Duplicate entry 'Foreign Key Constraint Violated!' for key 'PRIMARY'
ERROR 1062 (23000): Duplicate entry 'Foreign Key Constraint Violated!' for key 'PRIMARY'
Conclusion
In this article we examined how we could programmatically enforce foreign keys on storage engines which do not natively
support them. This was done by the use of triggers. The key advantage to leveraging these types of constraints is to
increase the integrity of the data, while simplifying the work programmers need to do to application code in order to achieve
this.