Removing the performance hit of ON UPDATE CASCADE
We need to discuss in a little more detail the ON UPDATE CASCADE behavior and its performance implications. Whenever a developer's status changes, the corresponding DeveloperStatus column in the Tickets
table changes automatically, for all the tickets assigned to that
developer. This behavior definitely gets the job done, and our business
rules are robustly enforced. However, we need to ask ourselves the
following question: why do we need to update the DeveloperStatus column for closed tickets? Clearly our CHECK constraint only needs to use DeveloperStatus if the ticket is open.
Why should we care if the DeveloperStatus
column is updated for closed tickets? The reason is simple:
performance. In our test cases, with just one or two developers and only
one ticket, it does not matter whether or not we update the DeveloperStatus
column for closed tickets. However, in real life, a developer might
have hundreds or even thousands of closed tickets assigned to him or
her, but just a handful of active ones. If we can avoid modifying those
closed tickets every time a developer goes on vacation, then we can
significantly improve the performance of the system.
Let us change our solution, so that the DeveloperStatus column is not updated for closed tickets. In order to do this, we need to ensure that Tickets.DeveloperStatusNULL for all closed tickets. Since open tickets can never be assigned to a developer on vacation, the result will be that Tickets.DeveloperStatus can now only be Active or NULL, and so the ON UPDATE CASCADE clause becomes redundant. is
Execute the script in Listing 15, which performs the following steps:
deletes the contents of both the Tickets and Developers tables (of course, in a production system we would take steps to preserve our data rather than delete it outright)
makes the Tickets.DeveloperStatus column nullable, because we need it to be NULL for closed tickets
modifies our CHK_Tickets_ValidStatuses constraint to enforce the rules that DeveloperStatus is NULL for all closed tickets, and is Active for all active ones
drops the FK_Tickets_Developers_WithStatus constraint and recreates it without the ON UPDATE CASCADE clause, which is now redundant.
We are now ready to run some tests against our changed implementation. Listing 16 inserts some fresh test data, including a closed ticket with NULL DeveloperStatus.
If we rerun Listing 11, it will raise an error, confirming that Justin cannot go on vacation, because there is an active ticket assigned to him
Now, when we close an open ticket, we also need to set DeveloperStatus to NULL, as shown in Listing 17.
At this moment, both tickets assigned to Justin are closed, and DeveloperStatus is NULL for both those tickets. This means that, for these tickets, the pair of columns (AssignedDeveloperID, DeveloperStatus) does not refer to any row in the Developers table, because DeveloperStatus is NULL. Note that, even for closed tickets, we still need to ensure that Tickets.AssignedDeveloperID refers to a valid Developers.DeveloperID; this is why we have another FOREIGN KEY constraint (FK_Tickets_Developers) on AssignedDeveloperID only.
Let's proceed with the testing. If Justin goes on vacation, we expect that no rows in the Tickets table should be modified, as verified by Listing 18.
If Justin is on vacation we cannot reopen a ticket assigned to him, as verified by Listing 19.
We have seen how three
constraints work together to enforce a rather complex business rule.
Originally, we used an approach that works in the simplest possible way,
and then we modified it to be more efficient. We have also removed the
side effect of changes in Developers.DeveloperID column propagating into the Tickets table (and our FK constraint will prohibit changes that would result in orphaned rows).
Let's move on to discuss another case where constraints really shine: inventory systems.