2016-09-21 115 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步:處理死鎖!他們是而不是完全可以預防。因此,計劃在發生死鎖時重複整個事務。

相關問題