2

外键可能是解决此问题的最佳方法。但是,我正在尝试了解表锁定/事务,因此我希望我们暂时可以忽略它们。

假设我在InnoDB数据库中有两个表:categoriesjokes; 并且我正在使用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可能返回不同的结果,我相信我需要研究我不熟悉的事务隔离级别。

在我看来,如果我做了这两件事,它应该会按预期工作。不过,我很想听到更明智的意见。谢谢。

4

1 回答 1

2

仅当没有匹配的笑话时,您才能删除类别:

DELETE c FROM categories AS c
LEFT OUTER JOIN jokes AS j ON c.id=j.category_id
WHERE c.id = $category_id AND j.category_id IS NULL;

如果该类别有任何笑话,联接将找到它们,因此外联接将返回非空结果。WHERE 子句中的条件消除了非空结果,因此整体删除将匹配零行。

同样,只有当类别存在时,您才能将笑话插入到类别中:

INSERT INTO jokes (category_id, joke_text)
SELECT c.id, '$joke_text'
FROM categories AS c WHERE c.id = $category_id;

如果没有这样的类别,则 SELECT 返回零行,并且 INSERT 是空操作。

这两种情况都会在类别表上创建一个共享锁(S-lock)。

S锁的演示:

在一个会话中,我运行:

mysql> INSERT INTO bar (i) SELECT SLEEP(600) FROM foo;

在第二次会议中,我运行:

mysql> SHOW ENGINE INNODB STATUS\G
. . .
---TRANSACTION 3849, ACTIVE 1 sec
mysql tables in use 2, locked 2
2 lock struct(s), heap size 376, 1 row lock(s)
MySQL thread id 18, OS thread handle 0x7faefe7d1700, query id 203 192.168.56.1 root User sleep
insert into bar (i) select sleep(600) from foo
TABLE LOCK table `test`.`foo` trx id 3849 lock mode IS
RECORD LOCKS space id 22 page no 3 n bits 72 index `GEN_CLUST_INDEX` of table `test`.`foo` trx id 3849 lock mode S

您可以看到这会在表 foo 上创建一个 IS 锁,并在我正在读取的表 foo 的一行上创建一个 S 锁。

任何混合读/写操作(例如SELECT...FOR UPDATE, INSERT...SELECT, )都会发生同样的事情CREATE TABLE...SELECT,以阻止正在读取的行在需要它们作为写操作的源时被修改。

IS-lock 是一种表级锁,可以防止对表进行 DDL 操作,因此没有人发出问题DROP TABLE,或者ALTER TABLE此事务依赖于表中的某些内容。

于 2013-08-09T19:10:07.660 回答