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_cntavail_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 替换为UUID_SHORT()s。

如果您想确保在您阅读和更新记录之间没有人可以更改记录,您必须使用SELECT ... FOR UPDATESELECT ... LOCK IN SHARE MODESTART TRANSACTION...内COMMIT,这需要ENGINE支持事务,例如 InnoDB,或LOCK 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 中的 item 的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 回答