【问题标题】:Mirror tables: triggers, deadlock and implicit commits镜像表:触发器、死锁和隐式提交
【发布时间】:2026-02-09 09:35:01
【问题描述】:

我有 2 个相似的表,例如 A 和 B。我想将 A 中的插入复制到 B,并将 B 中的插入复制到 A 以集成两个用户系统。我在每个上都配置了“插入触发器后”。示例:

DELIMITER $$
CREATE DEFINER = `root`@`localhost` TRIGGER
`after_A_INSERT`
AFTER INSERT ON `A`
FOR EACH ROW BEGIN 
INSERT INTO `B`
SET `id` = NEW.`id`,`name` = NEW.`name`;
END$$
DELIMITER ;

DELIMITER $$
CREATE DEFINER = `root`@`localhost` TRIGGER
`after_B_INSERT`
AFTER INSERT ON `B`
FOR EACH ROW BEGIN 
INSERT INTO `A`
SET `id` = NEW.`id`,`name` = NEW.`name`;
END$$
DELIMITER ;

如果我在 A 中插入,触发器会调用 B 中的插入,但此插入会在 B 中执行触发器,并且会发生死锁,从而避免无限循环。

在执行 INSERT 之前,我尝试编辑触发器以 DROP 另一个表触发器,然后在它之后再次创建它。示例:

DELIMITER $$
CREATE DEFINER = `root`@`localhost` TRIGGER
`after_B_INSERT`
AFTER INSERT ON `B`
FOR EACH ROW BEGIN 
DROP TRIGGER IF EXISTS `after_A_INSERT`;
INSERT INTO `A`
SET `id` = NEW.`id`, `name` = NEW.`name`;
/* And CREATE again here */
END$$
DELIMITER ;

但是,CREATE 是一种进行隐式提交的数据定义语言 (DDL) 语句。因此,这是无法做到的。

我尝试调用带有 DROP 的 PROCEDURE 来显式处理提交,但也不可能。

有什么建议可以镜像这 2 个表格吗?


更新:使用Bill Karwinsuggestion,我向每个表添加了一个origin 字段,每个表都有一个相应的默认值AB。然后,我按如下方式更改(DROP 和 reCREATE)触发器:

A 中的触发器:

...
BEGIN 
IF NEW.`origin`='A' THEN
    INSERT INTO `B`
        SET `id` = NEW.`id`, `name` = NEW.`name`,  `origin` = NEW.`origin`;
    END IF;
END

B 中的触发器:

...
BEGIN 
IF NEW.`origin`='B' THEN
    INSERT INTO `A`
        SET `id` = NEW.`id`, `name` = NEW.`name`, `origin` = NEW.`origin`;
    END IF;
END

【问题讨论】:

    标签: mysql stored-procedures triggers deadlock autocommit


    【解决方案1】:

    你需要一些方法来避免产生循环。

    我建议在两个表中添加一列 origin。在表 A 中,填写DEFAULT 'A'。在表 B 中,将DEFAULT 'B' 设为。

    在您的应用程序中插入任一表时,始终省略origin 列,使其采用默认值。

    在两个触发器中,仅当 NEW.origin 等于各自表的默认值时才复制到另一个表。


    您的评论和新错误:

    对不起,我忘了在触发器中插入另一个表时,还必须复制NEW.origin的值。只是在您的应用程序中,当您执行原始插入时,您是否省略了origin

    A 中的触发器:

    ...
    BEGIN 
    IF NEW.`origin`='A' THEN
        INSERT INTO `B`
            SET `id` = NEW.`id`, `name` = NEW.`name`, `origin` = NEW.`origin`;
        END IF;
    END
    

    B 中的触发器:

    ...
    BEGIN 
    IF NEW.`origin`='B' THEN
        INSERT INTO `A`
            SET `id` = NEW.`id`, `name` = NEW.`name`, `origin` = NEW.`origin`;
        END IF;
    END
    

    我创建了这些触发器,然后进行了测试:

    mysql> insert into A set name = 'bill';
    Query OK, 1 row affected (0.00 sec)
    
    mysql> insert into B set name = 'john';
    Query OK, 1 row affected (0.01 sec)
    
    mysql> select * from A;
    +----+------+--------+
    | id | name | origin |
    +----+------+--------+
    |  1 | bill | A      |
    |  2 | john | B      |
    +----+------+--------+
    2 rows in set (0.00 sec)
    
    mysql> select * from B;
    +----+------+--------+
    | id | name | origin |
    +----+------+--------+
    |  1 | bill | A      |
    |  2 | john | B      |
    +----+------+--------+
    

    【讨论】:

    • 非常感谢您抽出宝贵的时间!我测试了它,效果很好!