【问题标题】:Does MySQL InnoDB engine queue DB Triggers automatically?MySQL InnoDB 引擎是否自动排队 DB Triggers?
【发布时间】:2011-08-21 04:56:53
【问题描述】:

假设我在我们的服务器上收到了 1000 个更新单个 MySQL 表的请求。在这种情况下不可避免地会出现死锁问题。我们已按照建议的死锁重试发布事务,但它们仍然会发生。

我们正在考虑在下面提出一个替代解决方案。

  1. 创建表 A、B、C。
  2. 向服务器发送将表 D 更新到 A 或 B 或 C 的写入请求。
  3. 分别在表 A、B 和 C 上创建一个 INSERT 触发器,依次将数据写入表 D 中,而不是直接将表 D 暴露给到达服务器的 1000 个请求。

所以我们的问题是,当这种情况发生并且多行写入表 A、B 和 C 时,表 A、B 和 C 上的底层触发器可能会同时触发以更新表 D。

MySQL InnoDB 引擎会自动将这些触发器排队,还是我们必须在代码中处理这些?

非常感谢任何帮助。

现在由所有这些请求直接更新的表 D 以及发生死锁的位置如下所示。

v_user_email    varchar(60) NO  PRI     
v_device_IMEI   varchar(40) NO  PRI     
i_adid          int(11)         NO  PRI     
i_impressions   int(4)          YES 0   
dt_pulllogdttm  datetime    NO          
c_created_by    char(15)    NO          
dt_created_on   datetime    NO          
c_modified_by   char(15)    YES         
dt_modified_on  datetime    YES 

在此表中插入/更新行的 PHP 如下所示。您会看到,如果由于死锁而失败,我们会尝试发布 3 次事务,但即使在那时也有事务失败,并且日志显示由于死锁。

$updateQuery = "UPDATE tb_ad_pull_log SET i_impressions = (i_impressions + 1), dt_pulllogdttm = SYSDATE(), c_modified_by = '$createdBy', dt_modified_on = SYSDATE() WHERE v_user_email = '$email' AND i_adid = $adId";
        if(ExecuteDeadLockQuery($updateQuery, "UPDATE", __LINE__) == 0) // If there is no record for this ad for the user, insert a new record
        {
            $insertQuery = "INSERT INTO tb_ad_pull_log VALUES('$email', '$device_IMEI', $adId, 1, SYSDATE(), '$createdBy', SYSDATE(), NULL, NULL)";
            ExecuteDeadLockQuery($insertQuery, "INSERT", __LINE__);
        }    

ExecuteDeadLockQuery 函数长这样 -

function ExecuteDeadLockQuery($query, $activity, $lineNumber)
    {
        global $errorLoggingPath;
        $maxAttempts = 3;
        $currentTry = 1;
        $noOfAffectedRows = -1;

        while($currentTry <= $maxAttempts)
        {
            $currentTry++;

            mysql_query($query);

            if( mysql_errno() <> 0 ) // If error occured
            {
                continue;
            }
            else
            {
                $noOfAffectedRows = mysql_affected_rows();
                break;
            }           
        }

        if($noOfAffectedRows == -1) // Query never executed successfully
        {
            LogError($activity . " failed in tb_ad_pull_log: " . mysql_error(), __FILE__, $lineNumber , $errorLoggingPath);
        }

        return $noOfAffectedRows;
    }

有没有更简洁的方法来避免这种死锁?以下是我们拥有的一些日志。

ERROR:  08-21-2011 14:09:57  UPDATE failed in tb_ad_pull_log: Deadlock found when trying to get lock; try restarting transaction    LINE  83
ERROR:  08-21-2011 14:09:57  INSERT failed in tb_ad_pull_log: Deadlock found when trying to get lock; try restarting transaction    LINE  86
ERROR:  08-21-2011 14:09:57  INSERT failed in tb_ad_pull_log: Deadlock found when trying to get lock; try restarting transaction    LINE  86
ERROR:  08-21-2011 14:09:57  UPDATE failed in tb_ad_pull_log: Deadlock found when trying to get lock; try restarting transaction   LINE  83
ERROR:  08-21-2011 14:09:57  INSERT failed in tb_ad_pull_log: Deadlock found when trying to get lock; try restarting transaction    LINE  86
ERROR:  08-21-2011 14:09:57  UPDATE failed in tb_ad_pull_log: Deadlock found when trying to get lock; try restarting transaction    LINE  83
ERROR:  08-21-2011 14:09:59  UPDATE failed in tb_ad_pull_log: Deadlock found when trying to get lock; try restarting transaction    LINE  83
ERROR:  08-21-2011 14:09:59  UPDATE failed in tb_ad_pull_log: Deadlock found when trying to get lock; try restarting transaction    LINE  83
ERROR:  08-21-2011 14:10:01  UPDATE failed in tb_ad_pull_log: Deadlock found when trying to get lock; try restarting transaction    LINE  83
ERROR:  08-21-2011 14:10:01  INSERT failed in tb_ad_pull_log: Deadlock found when trying to get lock; try restarting transaction    LINE  86

第 83 行是 PHP 中的 UPDATE 语句,第 86 行是 INSERT。请记住,这些数据可以以每秒 5-8 个事务的速率写入此表。

其他信息

每次 INSERT 和 UPDATE 到 TABLE D 时,都会执行一个触发器来更新 TABLE X 和 TABLE Y。这是表 D 保持锁定并因此传入请求陷入死锁的原因吗?

终于找到了问题,但我不知道如何解决它。 TABLE D 上的 AFTER INSERT 和 AFTER UPDATE 触发器在触发时会锁定表,因此传入请求会发生死锁。为什么我如此确定这一点是因为一旦我删除了这些触发器,日志就会停止记录否则记录的死锁消息

触发器代码片段。

    CREATE DEFINER=CURRENT_USER TRIGGER tuadmin.t_update_CPM_updateBalance
AFTER UPDATE
ON tb_ad_pull_log
FOR EACH ROW
BEGIN

    DECLARE `cpm_value` decimal(10,4);
    DECLARE `clientid` int(4);

    /* Execute the below block if the requested ad is not the default ad */
    IF NEW.i_adid <> 1 THEN

        SET SESSION TRANSACTION ISOLATION LEVEL READ COMMITTED;
            //do updates to TABLE X and Y logic
END

这就是我不明白为什么这些触发器会锁定表 D 而不让任何插入/更新同时发生。

如果我们放弃触发器,只从 PHP 调用一个 SP 来完成这项工作,这会避免所有问题吗?

【问题讨论】:

  • 您的交易是什么样的?为了产生死锁,您应该有多个事务,它们已经更新了一行并尝试更新第二行,但该行已被另一个事务更新。 Simle 插入以及仅对 1 行的更新不会导致死锁。也许您需要按/限制更新查询排序?
  • @Darhazer 更新了包含交易详情的问题。非常感谢您的帮助。
  • 你有关于 v_user_email / i_adid 的索引吗?如果不是,那会导致死锁,导致更新查询必须扫描所有记录
  • @Darhazer v_user_email/v_device_IMEI/i_adid 是表的主键
  • 您是否对同一封电子邮件有多个查询?您可能知道,BTree 索引中字段的顺序很重要,因此该索引可用于 i_adid 查找,因为未提供 IMEI,并且查询必须锁定给定用户的所有记录。我正在研究这一点,因为仅使用 1 个表时发生死锁是罕见的情况,应该可以通过优化查询来避免。

标签: mysql triggers innodb deadlock


【解决方案1】:

好的,所以您使用的是一个表和几个触发器?

而你每秒只有很少的交易?

你有奇怪的锁定问题?

使用 PostgreSQL,我很确定以下几点: a) 不会有这些问题 b) 如果有,您将立即获得社区支持

您的问题有 99.99% 的可能性是由 VERY_SLOW_TRIGGERS 引起的,我的意思是非常非常慢,因为每秒只有 8 次意味着事务运行时间为 125 毫秒,这非常大。

锁的原因很明显,你在 D 表上调用了触发器。

-> call modification on table D
 -> before mod trigger
 -> modification
 -> after mod trigger
-> modification complete

I.E.触发器中发生的所有事情都是表 D 上事务的一部分,因此将保持锁定直到完成。

你可以:

a) 锁定更少的行

b) 减少锁定时间 -> 插入另一个表,从那里处理异步

c) 使用正确支持触发器的 rdbms

平衡选项是hammer-vs-fly 选项,没有理由需要多个服务器来实现如此低的 tps 计数。

但是,您应该对触发器的性能进行故障排除,并确认您没有在某处遇到 I/O 拥塞(通常不必要的缓慢也会过度使用宝贵的资源)。

好的,这是另一个选项:

UNLOCK TABLES 显式释放当前会话持有的所有表锁。

如果您的最后一个操作是更新/插入 并且如果您的触发器失败是不可能的或不是问题

然后您可以在触发器开始时使用它,释放所有锁并仅请求非锁定一致读取。

【讨论】:

    【解决方案2】:

    更新和插入 mysql 阻塞和同步操作,假设您有 2 个请求来自更新表 D 的 2 个触发器,当 1 正在更新表 D 时,第二个在队列中等待。对于 select 没有同步块 2 线程可以同时请求。如果你想让这个可能的同时交易,你应该建立复制

    【讨论】:

      【解决方案3】:

      在这种情况下,MYSQL DBA 使用的是一种称为“复制”的功能,即根据需要将单个服务器分成多个服务器以平衡负载。您可以使用单个强大的硬件来做到这一点,该硬件分为 2 个或更多虚拟服务器,在 VirtualBox、VirtualPC 或您的虚拟化风格的虚拟设备中运行,并启用 MYSQL 复制功能。

      您可以调整单个服务器的写入(在这种情况下是您的更新)和其他服务器的读取数据的查询。见MYSQL复制文档here

      【讨论】:

        猜你喜欢
        • 1970-01-01
        • 2016-12-23
        • 1970-01-01
        • 1970-01-01
        • 2012-10-02
        • 1970-01-01
        • 2019-08-18
        • 1970-01-01
        • 2019-01-14
        相关资源
        最近更新 更多