Foreign keys may be the best approach for this problem. However, I am trying to find out about the table lock / transaction, and therefore I hope that we can ignore them for now.
Suppose I have two tables in an InnoDB database: categories and jokes ; and that I use PHP / MySQLi to do the job. The tables look like this:
CATEGORIES id (int, primary, auto_inc) | category_name (varchar[64]) ============================================================ 1 knock, knock JOKES id (int, primary, auto_inc) | category_id (int) | joke_text (varchar[255]) ============================================================================= empty
Here are two functions, each of which calls a different connection, at the same time . Calls: delete_category(1) and add_joke(1,"Interrupting cow. Interrup-MOOOOOOOO!")
function delete_category($category_id) { // only delete the category if there are no jokes in it $query = "SELECT id FROM jokes WHERE category_id = '$category_id'"; $result = $conn->query($query); if ( !$result->num_rows ) { $query = "DELETE FROM categories WHERE id = '$category_id'"; $result = $conn->query($query); if ( $conn->affected_rows ) { return true; } } return false; } function add_joke($category_id,$joke_text) { $new_id = -1; // only add the joke if the category exists $query = "SELECT id FROM categories WHERE id = '$category_id'"; $result = $conn->query($query); if ( $result->num_rows ) { $query = "INSERT INTO jokes (joke_text) VALUES ('$joke_text')"; $result = $conn->query($query); if ( $conn->affected_rows ) { $new_id = $conn->insert_id; return $new_id; } } return $new_id; }
Now, if the SELECT both functions are executed at the same time, and proceed from there, delete_category will think that it will be possible to delete the category, and add_joke will think that it is normal to add a joke to the existing category, so I will get an empty categories table and an entry in the joke table , which refers to a category_id that does not exist.
Without using foreign keys, how would you solve this problem?
My best thought would be to do the following:
1) "LOCK TABLES categories WRITE, jokes WRITE" at the beginning of delete_category . However, since I use InnoDB, I really want to avoid locking entire tables (especially the main ones, which will be used often).
2) Executing the add_joke transaction, and then executing "SELECT id FROM categories WHERE id = '$category_id'" after inserting the record. If it does not exist at this point, the transaction is rolled back. However, since the two SELECT in add_joke may return different results, I believe that I need to study transaction isolation levels that I am not familiar with.
It seems to me that if I did both of these things, it would work as expected. However, I want to hear more informed opinions. Thanks.