外键可能是解决此问题的最佳方法。但是,我正在尝试了解表锁定/事务,因此我希望我们暂时可以忽略它们。
假设我在InnoDB数据库中有两个表:categories
和jokes
; 并且我正在使用PHP/MySQLi来完成这项工作。表格如下所示:
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
这里有两个函数,每个函数都被不同的连接同时调用。电话是:delete_category(1)
和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;
}
现在,如果SELECT
两个函数的语句同时执行,并从那里继续,delete_category
会认为可以删除类别,并add_joke
认为可以将笑话添加到现有类别中,所以我会得到一个空categories
表以及joke
表中引用不存在的条目category_id
。
如果不使用外键,你将如何解决这个问题?
到目前为止,我最好的想法是执行以下操作:
1)"LOCK TABLES categories WRITE, jokes WRITE"
在开始时delete_category
。但是,由于我使用的是 InnoDB,因此我非常希望避免锁定整个表(尤其是经常使用的主要表)。
2)做add_joke
一个事务,然后"SELECT id FROM categories WHERE id = '$category_id'"
在插入记录后做。如果此时不存在,则回滚事务。但是,由于其中的两个SELECT
语句add_joke
可能返回不同的结果,我相信我需要研究我不熟悉的事务隔离级别。
在我看来,如果我做了这两件事,它应该会按预期工作。不过,我很想听到更明智的意见。谢谢。