2

外部キーは、この問題に対する最良のアプローチである可能性があります。ただし、テーブルのロック/トランザクションについて学習しようとしているので、当面は無視できることを願っています。

InnoDBデータベースに 2 つのテーブルがある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

ここに 2 つの関数があり、それぞれが異なる接続によって同時に呼び出されています。呼び出しは次のとおりです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'" 後に実行する。その時点で存在しない場合は、トランザクションをロールバックします。ただし、 の 2 つの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;

カテゴリにジョークがある場合、結合はそれらを見つけるため、外部結合は null 以外の結果を返します。WHERE 句の条件によって null 以外の結果が排除されるため、全体的な削除はゼロ行に一致します。

同様に、カテゴリが存在する場合にのみ、ジョークをカテゴリに INSERT できます。

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

そのようなカテゴリがない場合、SELECT は 0 行を返し、INSERT はノーオペレーションです。

どちらの場合も、カテゴリ テーブルに共有ロック (S ロック) が作成されます。

S ロックのデモンストレーション:

私が実行する1つのセッションで:

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

2番目のセッションでは、次を実行します。

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 の 1 行に S ロックが作成されることがわかります。

SELECT...FOR UPDATEINSERT...SELECT、などのハイブリッド読み取り/書き込み操作でも同じことが起こりCREATE TABLE...SELECT、書き込み操作のソースとして必要な間、読み取り中の行が変更されるのをブロックします。

IS ロックは、テーブルでの DDL 操作を防止するテーブル レベルのロックであるため、このトランザクションがテーブル内のコンテンツに依存している間、誰も発行しDROP TABLEません。ALTER TABLE

于 2013-08-09T19:10:07.660 に答える