MySQL:生产者/消费者模型,访问多个表的命令需要原子性

标签 mysql stored-procedures stored-functions atomic

我有以下 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,两者的数量相同。

现在,当有多个生产者和消费者同时接触同一个项目时,我需要上述操作的原子性。

我想知道是否可以通过触发器解决这个问题? (我不想使用外部互斥体)任何人都可以向我指出如何执行此操作的示例示例吗?

最佳答案

根据您的request ,以下是重点关注代码中的性能问题的注释:

优化 add_item()

假设products_available_tblitem_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 //

我希望这些有帮助。让我知道你的想法!

关于MySQL:生产者/消费者模型,访问多个表的命令需要原子性,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/11593380/

相关文章:

MySQL 问题编写查询

unit-testing - 单元测试和存储过程

.net - 如何鼓励自己转用ORM?

sql - 存储过程错误: "Error converting data type nvarchar to uniqueidentifier"

mysql - 需要帮助使用在两个单独表的列之间进行算术运算的函数将列添加到一个表

php - 使用查询获取数组中的多个类别

php - 当有人关闭浏览器窗口时是否可以运行 php 函数?

mysql - 在mysql中对2列进行排序

sql - 我的sql触发器中if语句中的多个条件

arrays - 将数组变量作为参数传递给另一个函数