0

サンプル値を含む次の2つのテーブルがあります。

producer_tbl:
id (auto-inc, PK)  producer_id   item_id   item_added
       2               5            3          20

products_available_tbl:
item_id (PK)   avail_cnt   blocked_cnt
  3               9             2

これらにアクセスする方法は次のとおりです。

メーカーから商品が提供されたら、producer_tblに適切なデータを挿入します。products_available_tbl内のそれぞれのアイテムのavail_cntを同時にインクリメントします。

消費者がその特定のアイテムを欲しがっているとき、私は最初に(avail_cnt --blocked_cnt)を使用して、要求された数量が利用可能かどうかを確認します。その場合、blocked_cntを数量でインクリメントしますが、avail_cntは更新しません。コンシューマーが要求にコミットすると、blocked_cntとavail_cntを両方とも同じ量でデクリメントします。

さて、複数のプロデューサーとコンシューマーが同時に同じアイテムに触れている場合、上記の操作にはアトミック性が必要です。

トリガーでこれを解決できるかどうか知りたいのですが?(外部ミューテックスを使用したくない)これを行う方法のサンプル例を誰かに教えてもらえますか?

4

3 に答える 3

1

uuid VARCHAR(32)テーブルに列を追加することで、今読んだレコードをいつでも更新していることを確認できます。uuid更新するレコードを読み取り、フィールドが変更されていないことを確認してレコードを更新します。

たとえば、次の方法でインクリメントできますblocked_cnt

UPDATE products_available_tbl
   SET blocked_cnt = blocked_cnt + 1,
       uuid = UUID()
 WHERE blocked_cnt = 2
   AND uuid = '21EC2020-3AEA-1069-A2DD-08002B30309D';

SELECT ROW_COUNT(); -- a 1 indicates the UPDATE was successful, 0 or -1 failure

blocked_cntおよびavail_cntフィールドをデクリメントするには:

UPDATE products_available_tbl
   SET blocked_cnt = blocked_cnt - 1,
       avail_cnt = avail_cnt - 1,
       uuid = UUID()
 WHERE blocked_cnt = 3
   AND uuid = '3F2504E0-4F89-11D3-9A0C-0305E82C3301';

SELECT ROW_COUNT();

レコードごとに24バイトを節約するuuid_short BIGINTには、代わりにフィールドを使用して、UUID()上記のsをsに置き換えることができますUUID_SHORT()

レコードを読み取ってから更新するまでの間に誰もレコードを変更できないようにする場合は、 ...を使用するSELECT ... FOR UPDATEか、SELECT ... LOCK IN SHARE MODE内部で行う必要があります。これには、 InnoDBなどのトランザクションをサポートする、または/すべてで機能するが必要です。データベースエンジン。START TRANSACTIONCOMMITENGINELOCK TABLES READ [LOCAL]UNLOCK TABLES

于 2012-07-21T16:19:58.600 に答える
1

リクエストごとに、以下はコードのパフォーマンスの問題に焦点を当てたコメントです。

add_item()の最適化

products_available_tblに一意のインデックスがあると仮定するとitem_id

CREATE PROCEDURE add_item(IN in_producer_id INT, IN in_item_id INT,
                                                IN in_item_cnt INT)
BEGIN
    DECLARE item INT DEFAULT NULL;
    START TRANSACTION;

    INSERT INTO producer_tbl (producer_id, item_id, item_cnt)
        VALUES (in_producer_id, in_item_id, in_item_cnt);

    SELECT item_id FROM products_available_tbl 
        WHERE item_id=in_item_id INTO item FOR UPDATE;

    IF item IS NOT NULL THEN
        UPDATE products_available_tbl 
            SET avail_cnt=avail_cnt + in_item_cnt
            WHERE item_id=in_item_id;
    ELSE
        INSERT INTO products_available_tbl 
            (item_id, avail_cnt, blocked_cnt)
            VALUES (in_item_id, in_item_cnt, 0);
    END IF;

    COMMIT;
END //

次のように書き直すことができます:

CREATE PROCEDURE add_item(IN in_producer_id INT, IN in_item_id INT,
                                                IN in_item_cnt INT)
BEGIN
    START TRANSACTION;

    INSERT INTO producer_tbl (producer_id, item_id, item_cnt)
        VALUES (in_producer_id, in_item_id, in_item_cnt);

    INSERT INTO products_available_tbl SET
        item_id = in_item_id,
        avail_cnt = in_item_cnt,
        blocked_cnt = 0
    ON DUPLICATE KEY UPDATE
        avail_cnt = avail_cnt + in_item_cnt;

    COMMIT;
END //

block_item()の最適化

最適化は重要なので、段階的に進めましょう。

まず、書き直しましょう

SET out_cnt = var_avail_cnt - var_blocked_cnt;
IF out_cnt >= cnt THEN
    SET out_cnt = cnt;
END IF;

なので

SET out_cnt = LEAST(var_avail_cnt - var_blocked_cnt, cnt);

次に、書き直しましょう

SELECT avail_cnt, blocked_cnt FROM products_available_tbl
    WHERE item_id=in_item_id INTO var_avail_cnt, var_blocked_cnt
    FOR UPDATE;

SET out_cnt = LEAST(var_avail_cnt - var_blocked_cnt, cnt);

なので

SELECT LEAST(avail_cnt - blocked_cnt, cnt) FROM products_available_tbl
    WHERE item_id=in_item_id INTO out_cnt
    FOR UPDATE;

最後に、書き直しましょう

SELECT LEAST(avail_cnt - blocked_cnt, cnt) FROM products_available_tbl
    WHERE item_id=in_item_id INTO out_cnt
    FOR UPDATE;

UPDATE products_available_tbl
    SET blocked_cnt = var_blocked_cnt + out_cnt
    WHERE item_id = in_item_id;

なので

UPDATE products_available_tbl
    SET 
    blocked_cnt = blocked_cnt + (@out_cnt := LEAST(avail_cnt - blocked_cnt, cnt))
    WHERE item_id = in_item_id;

それで

CREATE PROCEDURE block_item(IN in_item_id INT, INOUT cnt INT)
BEGIN
    DECLARE out_cnt INT DEFAULT cnt;
    DECLARE var_avail_cnt, var_blocked_cnt INT DEFAULT 0;
    START TRANSACTION;

    SELECT avail_cnt, blocked_cnt FROM products_available_tbl
        WHERE item_id=in_item_id INTO var_avail_cnt, var_blocked_cnt
        FOR UPDATE;

    SET out_cnt = var_avail_cnt - var_blocked_cnt;
    IF out_cnt >= cnt THEN
        SET out_cnt = cnt;
    END IF;

    UPDATE products_available_tbl
        SET blocked_cnt = var_blocked_cnt + out_cnt
        WHERE item_id = in_item_id;

    SET cnt = out_cnt;
    COMMIT;
END //

になります

CREATE PROCEDURE block_item(IN in_item_id INT, INOUT cnt INT)
BEGIN
    UPDATE products_available_tbl
        SET 
        blocked_cnt = blocked_cnt + (@out_cnt := LEAST(avail_cnt - blocked_cnt, cnt))
        WHERE item_id = in_item_id;

    SET cnt = @out_cnt;
END //

commit_item()の最適化:

書き直そう

CREATE PROCEDURE commit_item(IN in_item_id INT, INOUT cnt INT)
BEGIN
    DECLARE out_cnt INT DEFAULT cnt;
    DECLARE var_avail_cnt, var_blocked_cnt INT DEFAULT 0;
    START TRANSACTION;

    SELECT avail_cnt, blocked_cnt FROM products_available_tbl
        WHERE item_id=in_item_id INTO var_avail_cnt, var_blocked_cnt
        FOR UPDATE;

    IF cnt > var_blocked_cnt THEN
        SET out_cnt = -1;  /* Error case: Caller supplied wrong value. */
    ELSEIF var_blocked_cnt > var_avail_cnt THEN
        SET out_cnt = -2;  /* Error case: Bug in block_item proc. */
    ELSE
        SET out_cnt = cnt;

        UPDATE products_available_tbl
            SET blocked_cnt = var_blocked_cnt - out_cnt,
                avail_cnt = var_avail_cnt - out_cnt
            WHERE item_id = in_item_id;
    END IF;

    SET cnt = out_cnt;
    COMMIT;
END //

なので

CREATE PROCEDURE commit_item(IN in_item_id INT, INOUT cnt INT)
proc: BEGIN
    DECLARE var_avail_cnt, var_blocked_cnt INT DEFAULT 0;

    UPDATE products_available_tbl
        SET blocked_cnt   = blocked_cnt - cnt,
            avail_cnt     = avail_cnt - cnt
        WHERE item_id     = in_item_id
        AND   cnt         <= blocked_cnt
        AND   blocked_cnt <= avail_cnt;

    IF ROW_COUNT() > 0 THEN
        LEAVE proc;
    END IF;

    SELECT avail_cnt, blocked_cnt FROM products_available_tbl
        WHERE item_id=in_item_id INTO var_avail_cnt, var_blocked_cnt;

    IF cnt > var_blocked_cnt THEN
        SET cnt = -1;  /* Error case: Caller supplied wrong value. */
    ELSEIF var_blocked_cnt > var_avail_cnt THEN
        SET cnt = -2;  /* Error case: Bug in block_item proc. */
    ELSE
        SET cnt = -3; /* UPDATE failed, reasons unknown. */
    END IF;
END //

これらの助けを願っています。どう考えているか教えてください!

于 2012-07-28T02:17:40.920 に答える
0

ストアドプロシージャを使用することは、複数の人がここで複数のテーブルにアクセス/変更するこのシナリオの問題を解決するための良い方法であることがわかりました。これにもアトミック性が必要です。アイテムを追加し、アイテムをブロックし、アイテムをコミットする3つの手順を次に示します。add_item操作は、products_available_tbl内のアイテムのavail_cntに常に追加されるため、実際にはアトミック性を必要としません。したがって、add_itemプロシージャは実際には必要ありません。

DELIMITER //
DROP PROCEDURE IF EXISTS `add_item` //
CREATE PROCEDURE add_item(IN in_producer_id INT, IN in_item_id INT,
                                                IN in_item_cnt INT)
BEGIN
    DECLARE item INT DEFAULT NULL;
    START TRANSACTION;

    INSERT INTO producer_tbl (producer_id, item_id, item_cnt)
        VALUES (in_producer_id, in_item_id, in_item_cnt);

    SELECT item_id FROM products_available_tbl 
        WHERE item_id=in_item_id INTO item FOR UPDATE;

    IF item IS NOT NULL THEN
        UPDATE products_available_tbl 
            SET avail_cnt=avail_cnt + in_item_cnt
            WHERE item_id=in_item_id;
    ELSE
        INSERT INTO products_available_tbl 
            (item_id, avail_cnt, blocked_cnt)
            VALUES (in_item_id, in_item_cnt, 0);
    END IF;

    COMMIT;
END //
DELIMITER ;

この「block_item」は、消費者がアイテムをリクエストしたが、購入を確約していない場合に呼び出されます。

DELIMITER //
DROP PROCEDURE IF EXISTS `block_item` //
CREATE PROCEDURE block_item(IN in_item_id INT, INOUT cnt INT)
BEGIN
    DECLARE out_cnt INT DEFAULT cnt;
    DECLARE var_avail_cnt, var_blocked_cnt INT DEFAULT 0;
    START TRANSACTION;

    SELECT avail_cnt, blocked_cnt FROM products_available_tbl
        WHERE item_id=in_item_id INTO var_avail_cnt, var_blocked_cnt
        FOR UPDATE;

    SET out_cnt = var_avail_cnt - var_blocked_cnt;
    IF out_cnt >= cnt THEN
        SET out_cnt = cnt;
    END IF;

    UPDATE products_available_tbl
        SET blocked_cnt = var_blocked_cnt + out_cnt
        WHERE item_id = in_item_id;

    SET cnt = out_cnt;
    COMMIT;
END //
DELIMITER ;

この「commit_item」は、顧客が注文を確認したときに呼び出されます。

DELIMITER //
DROP PROCEDURE IF EXISTS `commit_item` //
CREATE PROCEDURE commit_item(IN in_item_id INT, INOUT cnt INT)
BEGIN
    DECLARE out_cnt INT DEFAULT cnt;
    DECLARE var_avail_cnt, var_blocked_cnt INT DEFAULT 0;
    START TRANSACTION;

    SELECT avail_cnt, blocked_cnt FROM products_available_tbl
        WHERE item_id=in_item_id INTO var_avail_cnt, var_blocked_cnt
        FOR UPDATE;

    IF cnt > var_blocked_cnt THEN
        SET out_cnt = -1;  /* Error case: Caller supplied wrong value. */
    ELSEIF var_blocked_cnt > var_avail_cnt THEN
        SET out_cnt = -2;  /* Error case: Bug in block_item proc. */
    ELSE
        SET out_cnt = cnt;

        UPDATE products_available_tbl
            SET blocked_cnt = var_blocked_cnt - out_cnt,
                avail_cnt = var_avail_cnt - out_cnt
            WHERE item_id = in_item_id;
    END IF;

    SET cnt = out_cnt;
    COMMIT;
END //
DELIMITER ;

これらは正常に動作することがテストされています。

補足:Pythonを使用しているため、これらのプロシージャを呼び出した後、cursor.fetchone()を使用する必要があります。

于 2012-07-23T12:53:11.947 に答える