MySQL InnoDB引擎是否会自动排队数据库触发器?

时间:2011-08-21 04:56:53

标签: mysql triggers innodb deadlock

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

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

  1. 创建表A,B,C。
  2. 写入请求到服务器以将表D更新为A或B或C。
  3. 分别在表A,B和C上创建一个INSERT触发器,它将依次写入表D中的数据,而不是直接将表D暴露给发送到服务器的1000个请求。
  4. 所以我们的问题是当这种情况发生并且多行被写入表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次交易的速度写入此表。

    其他信息

    对表D中的每个INSERT和UPDATE都会执行一个更新TABLE X和TABLE Y的触发器。这是表D保持锁定的原因,因此传入的请求会出现死锁吗?

    终于遇到了问题,但我不知道如何解决它。表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来完成工作,这会避免所有问题吗?

3 个答案:

答案 0 :(得分: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

即。触发器中发生的一切都是表D上的事务的一部分,因此将保持锁定直到它完成。

你可以:

a)锁定较少的行

b)锁定更少的时间 - &gt;插入另一个表,从那里处理异步

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

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

但是,您应该对触发器的性能进行故障排除,并验证您是否在某处遇到了I / O拥塞(通常情况下,这种情况不必要地过度使用也会过度使用宝贵的资源)。

好的,这是另一种选择:

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

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

然后您可以在触发器的开头使用它,释放所有锁定并仅询问非锁定一致性读取。

答案 1 :(得分:1)

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

答案 2 :(得分:0)

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

您可以调整单个服务器以进行写入(在本例中为您的更新)和其他服务器以查找读取数据的查询。请参阅MYSQL复制文档here