【发布时间】:2016-09-21 20:49:27
【问题描述】:
我有一个包含数百万行的表,我必须使用按组对其进行计数。
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,因为这会导致死锁。所以必须同时创建两个客户记录才能引发死锁。
我的表和触发器系统有点复杂,但我尽量简化它以向您解释我的问题。
【问题讨论】:
-
你试过在
group_id上建立索引吗? -
@Solarflare 是的,两个表中的该列都有索引
-
“所以必须同时创建两个客户记录才能引发死锁” 这不是死锁的意思。同时两个是没有问题的。除非每个事务都持有另一个需要的锁,否则事务不会死锁,这表明您在单个事务中执行多个插入。你能证实吗?如果是这样,你为什么要这样做?如果您没有删除某些状态消息,也会更容易解释。
-
@Michael-sqlbot 这里是整个死锁消息pastebin.com/yK2vhEsh,查询有点复杂,因为我使用的不是“customer_stats”表,而是“table_stats”,因为它不是一个表我有一个计数器,但不止一个,而且我正在使用一张桌子将所有计数器放在一张桌子上。
-
和简化版本触发器的死锁日志(如上面的示例所示):pastebin.com/bWcjhEap 我的想法是死锁是因为我试图在单独的会话中更新 table_stats 表中的同一行,所以他们都得到
UPDATE SET value = 100+1,这是因为死锁是,因为MySQL中没有简单的增量函数并且它是基于值的,但我可能错了。
标签: php mysql triggers innodb deadlock