【问题标题】:SQLSTATE[40001]: Serialization failure: 1213 Deadlock issue caused by INSERT trigger on concurrent accessSQLSTATE [40001]:序列化失败:1213 并发访问上的 INSERT 触发器导致的死锁问题
【发布时间】:2020-02-26 12:01:33
【问题描述】:

我发现两个用户同时执行函数时会出现 SQL 死锁问题。我有一个 PHP 函数,它执行几个包含在事务中的数据库插入查询。其中一个插件也会触发触发器。请参阅下面的表架构和代码示例。

ma​​in_table

CREATE TABLE `main_table` (
  `id` INT NOT NULL AUTO_INCREMENT,
  `action_time` TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
  PRIMARY KEY (`id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8;

history_table

CREATE TABLE `history_table` (
  `id` INT NOT NULL AUTO_INCREMENT,
  `action_time` TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
  `audit_id` INT,
  PRIMARY KEY (`id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8;

审计表

CREATE TABLE `audit_table` (
  `id` INT NOT NULL AUTO_INCREMENT,
  `action_time` TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
  PRIMARY KEY (`id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8;

我在 main_table 上有一个触发器,定义如下。它的作用是从 audit_table 中选择最大 id 并将记录插入到 history_table

CREATE TRIGGER watch_insert_main_table
AFTER INSERT ON main_table
FOR EACH ROW BEGIN
    DECLARE max_id INT;
    SET max_id = (SELECT MAX(`id`) FROM `audit_table`) + 1;
    INSERT INTO `history_table` SET audit_id=max_id;
END;

以下是两个用户同时执行的功能。 insertRecord 函数只是将一条记录插入到给定的表中。

try {
    $dbConnection->beginTransaction();
    $this->insertRecord('main_table');    
    $this->insertRecord('audit_table');
    $dbConnection->commit();    
} catch (Exception $e) {
    $dbConnection->rollback();    
}

第二次(同时)调用该函数时出现以下死锁错误。

40001 - SQLSTATE[40001]: Serialization failure: 1213 Deadlock found when trying to get lock; try restarting transaction

如果我进行以下任一更改,则不会出现此问题。

  • 删除 PHP 代码中包含的事务
  • 从 PHP 代码中删除 $this->insertRecord('audit_table');
  • 修改触发器,使其没有来自 audit_table 的 select 语句

我想知道此问题的根本原因。另一个事务在触发时是否从 MySQL 触发器开始?触发器中的事务和锁是如何工作的?

我发现以下两个问题也与一个类似的问题有关。

【问题讨论】:

    标签: mysql concurrency triggers transactions innodb


    【解决方案1】:

    看看这个简化是否有帮助:

    CREATE TRIGGER watch_insert_main_table
    AFTER INSERT ON main_table
    FOR EACH ROW BEGIN
        INSERT INTO `history_table` (audit_id)
            SELECT MAX(`id`)+1 FROM `audit_table`;
    END;
    

    请注意它如何将您的两个语句组合成一个语句。这可能避免让另一个连接进入那里并获取相同的id(或类似的东西)。

    以下可能相关(由 OP 提供):这是由于 MySQL 中的 known issue 而发生的。使用未提交的读取隔离级别时,从选择中设置变量会获取锁。 This thread 有更多信息。

    您是否使用隔离模式读取未提交?如果有,为什么?

    【讨论】:

    • 是的!这解决了问题。我想知道的是这个问题的根本原因。我发现这个问题是由于 mysql 中的一个已知问题而发生的。我将编辑您的答案并添加更多信息。
    • @PathumHarshana - 我提供了建议代码的理由并挽救了您的评论,但是...您是否使用隔离模式阅读未提交?如果有,为什么?
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2019-10-18
    • 2016-05-13
    • 1970-01-01
    相关资源
    最近更新 更多