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

发布于 2024-11-30 18:31:58 字数 4879 浏览 0 评论 0原文

假设我们的服务器收到 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 个事务的速率写入此表。

其他信息

每次对表 D 进行 INSERT 和 UPDATE 时,都会执行一个触发器来更新表 X 和表 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 来完成工作,是否可以避免所有问题?

Let's say I have 1000 requests being received on our servers to update a single MySQL table. Deadlock issues occur inevitably in this situation. We have retried posting the transaction as recommended for deadlocks but they still occur.

We are thinking of coming up with an alternative solution below.

  1. Create Table A, B, C.
  2. Write Requests coming to the server to update Table D, into A or B or C.
  3. Create an INSERT trigger on Tables A, B and C respectively that will in turn write the data in Table D instead of directly exposing Table D to the 1000 requests coming to the server.

So our question is when this happens and multiple rows gets written into Table A, B and C the underlying Triggers on Tables A, B and C might fire at the same time to update Table D.

Does MySQL InnoDB engine automatically queue these triggers or should we have to handle this in our code?

Any help is much appreciated.

Table D that is being updated directly by all these requests now and where the deadlock occurs looks like this.

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 that inserts / updates rows in this table looks like this below. You will see that we try posting the transaction 3 times if it fails due to deadlock but there are transactions that fail even then and the log says due to deadlock.

$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 looks like this -

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;
    }

Is there a cleaner way to avoid this deadlock? Here are some logs that we have.

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

Line 83 is the UPDATE statement in the PHP and 86 is the INSERT. Please keep in mind that this data can be written into this table at the rate of 5-8 transactions per second.

Additional info

With every INSERT and UPDATE into TABLE D a trigger executes that updates TABLE X and TABLE Y. Is this a reason for Table D to remain locked and hence the incoming requests gets a deadlock?

Finally got the problem but I am not sure how to solve it. The AFTER INSERT and AFTER UPDATE triggers on TABLE D locks the table when they are triggered and hence the incoming requests deadlock. Why I am so sure of this is because once I dropped these triggers the log stopped logging deadlock messages logged otherwise

Snippet of the Trigger code.

    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

This is where I do not understand why would these triggers hold a lock on Table D and not let any insert/update happen concurrently.

Will this avoid all problems if we drop the triggers and just call a SP from the PHP to do the work?

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(3

雨后彩虹 2024-12-07 18:31:58

好的,那么您使用的是单个表和一些触发器?

而且每秒的交易量非常少?

你有奇怪的锁定问题吗?

使用 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) 锁定更少的时间 ->插入到另一个表中,从那里处理异步

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

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

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

好吧,这是另一个选项:

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

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

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

Ok, so you're using a single table, and a few triggers ?

And you have only very few transactions per second ?

And you have weird locking issues ?

Use PostgreSQL, i'm quite sure of the following :
a) It will not have those problems
b) If it has them, you'll have community support in no time

There is 99.99% of chances that your problem is caused by VERY_SLOW_TRIGGERS, I mean like totally hugely very slow, because only 8 per second implies a transaction running time of 125ms which is .. huge.

The reason for the lock is evident, you are calling a trigger on the table D.

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

I.E. everything that happens in your trigger is part of the transaction on table D, and will thus keep the lock until it's finished.

You can either :

a) lock less rows

b) lock less time -> insert into another table, process async from there

c) use a rdbms that supports triggers properly

The balancing option is the hammer-vs-fly option, there is no reason you would need more than one server for such low tps count.

However, you should troubleshoot the performance of your trigger and verify that you're not running into I/O congestion somewhere (usually that which is unnecessarily slow tends to also overuse precious resources).

Alright, here's another option :

UNLOCK TABLES explicitly releases any table locks held by the current session.

IF your last action is that update / insert
AND IF your trigger failing is either IMPOSSIBLE or NOT AN ISSUE

Then you could use this at the beginning of your trigger, releasing all locks and asking only for the non-locking consistent read.

凶凌 2024-12-07 18:31:58

mysql阻塞和同步操作中的更新和插入,假设您有2个请求来自2个更新表D的触发器,当1个更新表D时,第二个在队列上等待。对于select,没有同步块2线程可以请求同一时间。如果您想同时进行此事务,您应该构建复制

update and insert in mysql blocked and syncronized opration,assume that you have 2 request is coming from 2 trigger for updating table D ,when 1 is updating table D second is waited on queue.for select there has no syncronized block 2 thread can request at same time.if you want to make this possible same time transaction you should build replication

哆兒滾 2024-12-07 18:31:58

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

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

In this case what MYSQL DBAs use to have is a feature called "replication", that is divide a single server into many servers as needed to balancing loads. You can do that using a single powerful hardware, divided into 2 or more virtual servers running inside virtual appliances with VirtualBox, VirtualPC, or your virtualization flavor, with MYSQL replication feature enabled.

You can tune a single server for writes (your updates in this case) and other servers for queries that reads data. See MYSQL replication documentation here

~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文