简体   繁体   English

MySQL - 触发器调用两次导致死锁

[英]MySQL - trigger called twice causes deadlock

I have a table with millions of rows and I have to use count it divided by groups.我有一个包含数百万行的表,我必须使用按组划分的计数。

CREATE TABLE `customers` (
    `id` INT(10) UNSIGNED NOT NULL AUTO_INCREMENT,
    `group_id` INT(10) UNSIGNED NULL DEFAULT NULL
)

so calls I made very often are所以我经常打的电话是

SELECT COUNT(*) FROM customers WHERE group_id=XXX

But unfortunately MySQL is really slow (>10 sec for one call) when counting in tables with dozens millions of rows.但不幸的是,在数百万行的表中计数时,MySQL 真的很慢(一次调用超过 10 秒)。

So I decided to create a new table to keep counters only:所以我决定创建一个新表来只保留计数器:

CREATE TABLE `customer_stats` (
    `group_id` INT(11) NOT NULL,
    `value` INT(11) NOT NULL,
)

where I can keep current counters and make sure it's up to date using triggers.我可以在其中保留当前计数器并使用触发器确保它是最新的。

So I have a triggers for insert/update/delete, here's example of insert one:所以我有一个插入/更新/删除的触发器,这是插入一个的例子:

CREATE TRIGGER `customers_insert` AFTER INSERT ON `customers` FOR EACH ROW 
BEGIN
    UPDATE customer_stats
    SET
      `value` = `value` + 1
    WHERE
      customer_stats.group_id = NEW.group_id;
END

and it works fine for most cases, but on high load (dozens of calls per seconds) I got deadlocks.并且在大多数情况下都可以正常工作,但是在高负载(每秒数十次调用)时我陷入了死锁。

2016-09-21T20:14:30.639907Z 2057 [Note] InnoDB: Transactions deadlock detected, dumping detailed information.
2016-09-21T20:14:30.639926Z 2057 [Note] InnoDB:
*** (1) TRANSACTION:

TRANSACTION 10390, ACTIVE 0 sec starting index read
mysql tables in use 2, locked 2
LOCK WAIT 10 lock struct(s), heap size 1136, 5 row lock(s), undo log entries 1
MySQL thread id 2059, OS thread handle 140376644818688, query id 85330 test_test-php-fpm_1.test_default 172.19.0.12 root updating
UPDATE customer_stats
SET
  `value` = `value` + 1
WHERE
  customer_stats.group_id = NEW.group_id;
2016-09-21T20:14:30.639968Z 2057 [Note] InnoDB: *** (1) WAITING FOR THIS LOCK TO BE GRANTED:

RECORD LOCKS space id 85 page no 3 n bits 72 index customer_stats_key_group_id_unique of table `test`.`customer_stats` trx id 10390 lock_mode X locks rec but not gap waiting
Record lock, heap no 4 PHYSICAL RECORD: n_fields 5; compact format; info bits 0
 0: len 21; hex 637573746f6d657264657461696c735f636f756e74; asc customerdetails_count;;
 1: len 4; hex 80000002; asc     ;;
 2: len 6; hex 000000002890; asc     ( ;;
 3: len 7; hex 34000002341224; asc 4   4 $;;
 4: len 4; hex 80000666; asc    f;;

2016-09-21T20:14:30.640302Z 2057 [Note] InnoDB: *** (2) TRANSACTION:

TRANSACTION 10391, ACTIVE 0 sec starting index read
mysql tables in use 2, locked 2
10 lock struct(s), heap size 1136, 5 row lock(s), undo log entries 1
MySQL thread id 2057, OS thread handle 140376513820416, query id 85333 test_test-php-fpm_1.test_default 172.19.0.12 root updating
UPDATE customer_stats
SET
  `value` = `value` + 1
WHERE
  customer_stats.group_id = NEW.group_id;
2016-09-21T20:14:30.640334Z 2057 [Note] InnoDB: *** (2) HOLDS THE LOCK(S):

2016-09-21T20:14:30.640850Z 2057 [Note] InnoDB: *** WE ROLL BACK TRANSACTION (2)

It exists only on high load and I wonder if there is some easy way to change the trigger to make sure, that they don't try to execute that UPDATE customer_stats in the same time, as this is causing deadlock.它仅在高负载时存在,我想知道是否有一些简单的方法可以更改触发器以确保它们不会尝试同时执行UPDATE customer_stats ,因为这会导致死锁。 So two customer record must be created in the same time to raise deadlock.因此必须同时创建两个客户记录以引发死锁。

Tables and system of triggers I have is a little bit more complicated, but I tried to simplify it as much I can to explain you what is my problem.我的触发器表和系统有点复杂,但我尽量简化它以向您解释我的问题是什么。

You need a composite INDEX(key, group_id) , in either order.您需要一个复合INDEX(key, group_id) ,无论顺序。

Let's simplify the trigger: Step 1: VALUES is simpler than a SELECT :让我们简化触发器:第 1 步: VALUESSELECT简单:

BEGIN
    DECLARE originalGroupId INT;
    SET originalGroupId = NEW.group_id;
    INSERT IGNORE INTO table_stats(`key`, value, group_id)
        VALUES ('customers_count', 0, originalGroupId);   -- line changed
    UPDATE table_stats
    SET
      table_stats.`value` = table_stats.`value` + 1
    WHERE
      table_stats.`key` = "customers_count"
      AND table_stats.group_id = originalGroupId;
END

Step 2: Use IODKU.第二步:使用IODKU。 At this point, that needs to be UNIQUE(key, group_id) , in either order.在这一点上,这需要是UNIQUE(key, group_id) ,无论顺序。

BEGIN
    DECLARE originalGroupId INT;
    SET originalGroupId = NEW.group_id;
    INSERT INTO table_stats(`key`, value, group_id)
        VALUES ('customers_count', 1, originalGroupId)  -- note 1 not 0
        ON DUPLICATE KEY UPDATE
            `value` = `value` + 1;
END

Steps 1 and 2 make it run faster, thereby decreasing the frequency of deadlock.第 1 步和第 2 步使它运行得更快,从而降低了死锁的频率。

Step 3: Deal with deadlocks!第 3 步:处理死锁! They are not totally preventable.它们不是完全可以预防的。 So, plan on repeating the entire transaction whenever a deadlock occurs.因此,计划在发生死锁时重复整个事务。

ok, I think I found out what was the problem.好的,我想我发现了问题所在。

I tried to simplify the problem to show you here, but it looked like after I made it simpler - the problem was not existing anymore.我试图简化问题以在这里向您展示,但在我简化之后看起来 - 问题不再存在。

My original trigger was:我原来的触发器是:

BEGIN
    DECLARE originalGroupId INT;
    SET originalGroupId = NEW.group_id;
    INSERT IGNORE INTO table_stats(`key`, value, group_id)
    SELECT 'customers_count', 0, originalGroupId;
    UPDATE table_stats
    SET
      table_stats.`value` = table_stats.`value` + 1
    WHERE
      table_stats.`key` = "customers_count"
      AND table_stats.group_id = originalGroupId;
END

and I looks like deadlock is caused by INSERT IGNORE or variable as when I deleted it - it started to work without any issues.我看起来死锁是由INSERT IGNORE或变量引起的,就像我删除它时一样 - 它开始工作没有任何问题。 Thanks!谢谢!

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM