2016-09-21 128 views
1

我有一个包含数百万行的表,我不得不使用数除以组。MySQL - 触发器调用两次会导致死锁

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

所以叫我做很多时候是

SELECT COUNT(*) FROM customers WHERE group_id=XXX

但不幸的是MySQL是很慢(> 10秒为一个呼叫)在几十上百万行的表计数时。

所以我决定创建一个新表,只保留计数器:

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

在那里我可以保持当前计数器,并确保它是由使用触发器日期。

所以我有一个触发器插入/更新/删除,这里的例子插入之一:

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 

,并在大多数情况下工作得很好,但在高负荷(几十每秒电话)我有死锁。

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) 

它只存在于高负荷,我不知道是否有改变触发,以确保一些简单的方法,他们不尝试在同一时间执行该UPDATE customer_stats,因为这是造成僵局。因此,必须在同一时间创建两个客户记录以提高死锁率。

触发器的表格和系统我有点复杂一点,但我尽量简化它,我可以解释你是什么问题。

+1

你试过'group_id'上的索引吗? – Solarflare

+0

@Solarflare是的,在这两个表 – atay

+1

*的列中都有一个索引。“因此,必须在同一时间创建两个客户记录才能产生死锁”*这不是死锁意味着什么。两个在同一时间不是问题。事务不会死锁,除非每个人都拥有另一个人需要的锁,这表明您正在一次事务中执行多次插入。你能证实吗?如果是这样,你为什么这样做?如果你没有删除一些状态信息,它也会更容易解释。 –

回答

0

好的,我想我发现了什么问题。

我试图简化这个问题在这里展示给你,但看起来好像我简化后 - 问题不再存在。

我最初的导火索是:

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 

,我看起来像僵局被INSERT IGNORE或可变的,当我删除了引起的 - 它开始没有任何问题的工作。谢谢!

1

您需要一个复合INDEX(key, group_id),以任意顺序。

让我们简化触发:第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 

第2步:使用IODKU。在这一点上,这需要以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 

步骤1和2使其运行更快,从而降低死锁的频率。

第3步:处理死锁!他们是而不是完全可以预防。因此,计划在发生死锁时重复整个事务。

相关问题