在重叠集上执行并发级联更新查询时是否需要显式行锁?

时间:2016-11-25 10:53:43

标签: mysql stored-procedures locking mariadb

我有树数据结构,我存储在下表中:

#stores the actual node data
CREATE TABLE nodes(
    nodeId MEDIUMINT NOT NULL AUTO_INCREMENT,
    nodeType VARCHAR(10) NOT NULL, #'leaf', 'parallel' or 'sequential'
    nodeValue MEDIUMINT NOT NULL DEFAULT 0,
    PRIMARY KEY (nodeId) 
);

#stores the parent/child relationships
CREATE TABLE parents(
    nodeId MEDIUMINT NOT NULL,
    parentId MEDIUMINT NULL, # null if it is the root node
    PRIMARY KEY (nodeId),
    INDEX (parentId),
    FOREIGN KEY (nodeId) REFERENCES nodes(nodeId),
    FOREIGN KEY (parentId) REFERENCES nodes(nodeId)
);

树中的节点可以有三种类型,'leaf'节点是树的叶节点,用户可以随时在{1}}和'parallel'个节点上设置任意值只能是分支节点,'sequential'节点的值必须始终等于其直接子节点的'parallel'max(value)节点的值必须始终为等于其直接子节点的'sequential'。用户还可以将分支节点从sum(value)切换到'sequential',反之亦然。所有这些操作必须将其更改级联到树中,以便数据始终保持一致。我有一些存储过程来执行此操作(如下所示),但我担心可能无法保证正确进行并发更改。

'parallel'

我的存储过程的事务中的默认行锁定是否保证如果两个单独的更改导致同时更新相同的节点(例如树的根节点将始终通过每次更改更新),那么一组在计算和应用以下一组更改之前将应用更改,或者我是否需要在更改这些行之前在构成将要更改的父谱系的所有节点行上显式设置锁定?

实施例

(p) - 平行,(s) - 顺序 具有节点值的树:

DROP PROCEDURE IF EXISTS setLeafNodeValue;
DELIMITER $$
CREATE PROCEDURE setLeafNodeValue(id MEDIUMINT, val MEDIUMINT)
proc:BEGIN
    # assume validation checks to ensure it exists and is a leaf node have already been done
    DECLARE currentNodeId MEDIUMINT DEFAULT id;
    DECLARE currentNodesNewValue MEDIUMINT DEFAULT 0;
    DECLARE currentNodesOldValue MEDIUMINT DEFAULT 0;
    DECLARE currentNodeType VARCHAR(10) DEFAULT NULL;
    DECLARE prevNodesOldValue MEDIUMINT DEFAULT 0;
    DECLARE prevNodesNewValue MEDIUMINT DEFAULT 0;

    START TRANSACTION;

        WHILE currentNodeId IS NOT NULL DO

            SELECT nodeType, nodeValue INTO currentNodeType, currentNodesOldValue FROM nodes WHERE nodeId = currentNodeId;

            CASE currentNodeType
                WHEN 'leaf' THEN
                    SET currentNodesNewValue = val;
                WHEN 'parallel' THEN
                    SET currentNodesNewValue = (SELECT MAX(n.nodeValue) FROM nodes n JOIN parents p ON n.nodeId = p.nodeId WHERE p.parentId = currentNodeId);
                WHEN 'sequential' THEN
                    SET currentNodesNewValue = currentNodesOldValue + (prevNodesNewValue - prevNodesOldValue);
            END CASE;

            IF currentNodesNewValue = currentNodesOldValue THEN
                COMMIT;
                LEAVE proc; #no change is being made
            END IF;

            UPDATE nodes SET nodeValue = currentNodesNewValue WHERE nodeId = currentNodeId;

            SET prevNodesOldValue = currentNodesOldValue;

            SET prevNodesNewValue = currentNodesNewValue;

            SELECT parentId INTO currentNodeId FROM parents WHERE nodeId = currentNodeId;

        END WHILE;

    COMMIT;
END$$
DELIMITER ;

DROP PROCEDURE IF EXISTS setBranchNodeType;
DELIMITER $$
CREATE PROCEDURE setBranchNodeType(id MEDIUMINT, newNodeType VARCHAR(10))
proc:BEGIN
    # assume validation checks to ensure it exists and is a branch node have already been done
    # and that newNodeType is either 'parallel' or 'sequential'
    DECLARE currentNodeId MEDIUMINT DEFAULT id;
    DECLARE currentNodesNewValue MEDIUMINT DEFAULT 0;
    DECLARE currentNodesOldValue MEDIUMINT DEFAULT 0;
    DECLARE currentNodeType VARCHAR(10) DEFAULT NULL;
    DECLARE prevNodesOldValue MEDIUMINT DEFAULT 0;
    DECLARE prevNodesNewValue MEDIUMINT DEFAULT 0;

    SELECT nodeType, nodeValue INTO currentNodeType, currentNodesOldValue FROM nodes WHERE nodeId = currentNodeId;

    IF currentNodeType = newNodeType THEN
        LEAVE proc;
    END IF;

    START TRANSACTION;

        CASE newNodeType
            WHEN 'parallel' THEN
                SET currentNodesNewValue = (SELECT MAX(n.nodeValue) FROM nodes n JOIN parents p ON n.nodeId = p.nodeId WHERE p.parentId = currentNodeId);
            WHEN 'sequential' THEN
                SET currentNodesNewValue = (SELECT SUM(n.nodeValue) FROM nodes n JOIN parents p ON n.nodeId = p.nodeId WHERE p.parentId = currentNodeId);
        END CASE;

        UPDATE nodes SET nodeType = newNodeType, nodeValue = currentNodesNewValue WHERE nodeId = currentNodeId;

        IF currentNodesNewValue = currentNodesOldValue THEN
            COMMIT;
            LEAVE proc;
        END IF;

        SET prevNodesOldValue = currentNodesOldValue;

        SET prevNodesNewValue = currentNodesNewValue;

        SELECT parentId INTO currentNodeId FROM parents WHERE nodeId = currentNodeId;

        WHILE currentNodeId IS NOT NULL DO

            SELECT nodeType, nodeValue INTO currentNodeType, currentNodesOldValue FROM nodes WHERE nodeId = currentNodeId;

            CASE currentNodeType
                WHEN 'parallel' THEN
                    SET currentNodesNewValue = (SELECT MAX(n.nodeValue) FROM nodes n JOIN parents p ON n.nodeId = p.nodeId WHERE p.parentId = currentNodeId);
                WHEN 'sequential' THEN
                    SET currentNodesNewValue = currentNodesOldValue + (prevNodesNewValue - prevNodesOldValue);
            END CASE;

            IF currentNodesNewValue = currentNodesOldValue THEN
                COMMIT;
                LEAVE proc;
            END IF;

            UPDATE nodes SET nodeValue = currentNodesNewValue WHERE nodeId = currentNodeId;

            SET prevNodesOldValue = currentNodesOldValue;

            SET prevNodesNewValue = currentNodesNewValue;

            SELECT parentId INTO currentNodeId FROM parents WHERE nodeId = currentNodeId;

        END WHILE;

    COMMIT;
END$$
DELIMITER ;

如果一个用户将(p)4 | |__(s)3 | | | |__1 | | | |__2 | |__(p)4 | |__3 | |__4 #this test data can be seeded with INSERT INTO nodes (nodeType, nodeValue) VALUES ('parallel', 4), ('sequential', 3), ('parallel', 4), ('leaf', 1), ('leaf', 2), ('leaf', 3), ('leaf', 4); INSERT INTO parents (nodeId, parentId) VALUES (1, null), (2, 1), (3, 1), (4, 2), (5, 2), (6, 3), (7, 3); 值更改为1而另一个用户将其父3设置为(s)3,则最终结果应为:

parallel

但是如果更改应用于节点行的顺序无法保证,如果根节点在某个中间状态下计算,则可能导致根节点具有值(p)4 | |__(p)3 | | | |__3 | | | |__2 | |__(p)4 | |__3 | |__4 。首先处理哪个变更并不重要,只是它们的处理方式确保它们不会重叠并且在它们两个层叠树时相互干扰。

1 个答案:

答案 0 :(得分:0)

阅读文档我认为答案是我需要使用额外的锁来确保行为正确。我相信我需要:

SET TRANSACTION ISOLATION LEVEL SERIALIZABLE;
SET autocommit=0;

在我的START TRANSACTION;语句之前,为了锁定在给定更改中更新的节点的沿袭,并防止这些存储过程的其他并发执行相互冲突。这样,所有SELECT语句都会隐式转换为SELECT ... LOCK IN SHARE MODE,并且它们将一直等到任何现有的重叠沿袭操作完全完成。但是,在进行更新时,这不应该阻止其他简单读取读取节点。

所以setLeafNodeValue sp看起来像:

DROP PROCEDURE IF EXISTS setLeafNodeValue;
DELIMITER $$
CREATE PROCEDURE setLeafNodeValue(id MEDIUMINT, val MEDIUMINT)
proc:BEGIN
    # assume validation checks to ensure it exists and is a leaf node have already been done
    DECLARE currentNodeId MEDIUMINT DEFAULT id;
    DECLARE currentNodesNewValue MEDIUMINT DEFAULT 0;
    DECLARE currentNodesOldValue MEDIUMINT DEFAULT 0;
    DECLARE currentNodeType VARCHAR(10) DEFAULT NULL;
    DECLARE prevNodesOldValue MEDIUMINT DEFAULT 0;
    DECLARE prevNodesNewValue MEDIUMINT DEFAULT 0;

    SET TRANSACTION ISOLATION LEVEL SERIALIZABLE;
    SET autocommit=0;
    START TRANSACTION;

        WHILE currentNodeId IS NOT NULL DO

            SELECT nodeType, nodeValue INTO currentNodeType, currentNodesOldValue FROM nodes WHERE nodeId = currentNodeId;

            CASE currentNodeType
                WHEN 'leaf' THEN
                    SET currentNodesNewValue = val;
                WHEN 'parallel' THEN
                    SET currentNodesNewValue = (SELECT MAX(n.nodeValue) FROM nodes n JOIN parents p ON n.nodeId = p.nodeId WHERE p.parentId = currentNodeId);
                WHEN 'sequential' THEN
                    SET currentNodesNewValue = currentNodesOldValue + (prevNodesNewValue - prevNodesOldValue);
            END CASE;

            IF currentNodesNewValue = currentNodesOldValue THEN
                COMMIT;
                SET autocommit=1;
                LEAVE proc; #no change is being made
            END IF;

            UPDATE nodes SET nodeValue = currentNodesNewValue WHERE nodeId = currentNodeId;

            SET prevNodesOldValue = currentNodesOldValue;

            SET prevNodesNewValue = currentNodesNewValue;

            SELECT parentId INTO currentNodeId FROM parents WHERE nodeId = currentNodeId;

        END WHILE;

    COMMIT;
    SET autocommit=1;
END$$
DELIMITER ;