2016-11-17 69 views
0

我一直有一个特定的更新查询,这似乎应该是非常没有问题的各种麻烦。我已经改变了名称,但该表是:Innodb更新锁定

CREATE TABLE `problem_table` (
    `id` int(11) NOT NULL, 
    `type` enum('TYPE1','TYPE2','TYPE3') NOT NULL, 
    `date` datetime NOT NULL, 
    `reference_id` int(11) DEFAULT NULL, 
    `value` varchar(255) NOT NULL, 
    `source` varchar(16) DEFAULT NULL, 
    `problem_field` datetime DEFAULT NULL, 
    PRIMARY KEY (`id`), 
    KEY `type_idx` (`type`), 
    KEY `value_idx` (`value`(12)), 
    KEY `latest_id` (`reference_id`) 
) ENGINE=InnoDB DEFAULT CHARSET=latin1 

和查询造成的问题是:

UPDATE problem_table SET problem_field = 20000101 WHERE id = 6526153; 

值的problem_fieldid这里就不似乎是很重要的。

这个单一的更新是在problem_table上的各种选择查询反复死锁,所以我的问题是 - 这个简单的更新查询取出了什么锁?我应该补充说,这两个死锁事务只包含单个查询。

我已阅读the docs但他们似乎并不特别全面。

供参考,在这里是它和它的INNODB状态报告死锁的查询,虽然这仅仅是一个例子了许多不同的查询:

INSERT INTO temp 
SELECT 
    p.*, 
    DATE(p.date) 
FROM 
    problem_table p 
WHERE p.type IN ('TYPE1', 'TYPE2') 
    AND p.source = 'FOO'; 


------------------------ 
LATEST DETECTED DEADLOCK 
------------------------ 
161107 0:00:00 
*** (1) TRANSACTION: 
TRANSACTION 3C7788A94, ACTIVE 69 sec starting index read 
mysql tables in use 1, locked 1 
LOCK WAIT 10 lock struct(s), heap size 1248, 7 row lock(s), undo log entries 6 
MySQL thread id 6558222, OS thread handle 0x7f44a606d700, query id 3110073624 164.55.80.105 sym_dbuser Updating 
-- user=XXX progname=XXX host=XXX pid=XXX ldsn=XXX 
-- DBI::db=HASH(0x1d15ecb0) 
UPDATE problem_table SET problem_field = 'XXXX-XX-XX XX:XX:XX'WHERE id = 'XXXXX' 
*** (1) WAITING FOR THIS LOCK TO BE GRANTED: 
RECORD LOCKS space id 1069083 page no 313 n bits 280 index `PRIMARY` of table `XXX`.`problem_table` trx id 3C7788A94 lock_mode X locks rec but not gap waiting 
*** (2) TRANSACTION: 
TRANSACTION 3C766F450, ACTIVE 831 sec fetching rows, thread declared inside InnoDB 39 
mysql tables in use 2, locked 2 
47612 lock struct(s), heap size 5339576, 9395927 row lock(s), undo log entries 9194153 
MySQL thread id 6558799, OS thread handle 0x7f4203cb6700, query id 3108758081 172.29.1.16 XXX Sending data 
-- user=XXX progname=XXX host=XXX pid=XXX [email protected] 

INSERT INTO temp 
SELECT 
    p.*, 
    DATE(p.date) 
FROM 
    problem_table p 
WHERE p.type IN ('TYPE1', 'TYPE2') 
    AND p.source = 'FOO'; 
*** (2) HOLDS THE LOCK(S): 
RECORD LOCKS space id 1069083 page no 313 n bits 280 index `PRIMARY` of table `XXX`.`problem_table` trx id 3C766F450 lock mode S 
*** (2) WAITING FOR THIS LOCK TO BE GRANTED: 
RECORD LOCKS space id 1069083 page no 82008 n bits 280 index `PRIMARY` of table `XXX`.`problem_table` trx id 3C766F450 lock mode S waiting 
*** WE ROLL BACK TRANSACTION (2) 

编辑:

对于任何人的利益读下来,我发现INNODB能够检测3个或更多事务的死锁,但它只列出受害者和希望受害者锁定在死锁报告中的事务 - 其余事务不是在那里列出。

看到这一点,运行三个交易,如:

T(ransaction)1 take S lock on R(ecord) 1 
T2 take S lock on R2 
T2 take X lock on R1 (hangs waiting for T1) 
T3 take S lock on R3 
T3 take X lock on R2 (hangs waiting for T2) 
T1 take X lock on R3 (deadlock detected) 
+0

nope,它是无触发的 – rbennett485

+0

它看起来像INSERT INTO临时SELECT ... - 查询锁定problem_table。所以如果这个INSERT INTO查询发生,你的更新查询就会等待。根据行数量(超过9百万个被锁定),这可能会导致更新查询失败,因为在有用的时间内无法获得日志。你有一个缓慢的查询日志,你能看到,这些插入查询需要多长时间以及它们执行的频率? – Seb

+0

@Seb我确实有一个缓慢的查询日志,但这些更新没有出现在它中 - 它们只是出现死锁,在这种情况下,其他事务总是受害者 – rbennett485

回答

0

要回答这个问题埋在中间有一个基本问题,通过

UPDATE problem_table SET problem_field = 20000101 WHERE id = 6526153; 

其中problem_table被定义为在问题中取出锁只是:

事实证明,事情并不像我之前所描述的那样 - 工具1执行有问题的更新不会执行事务管理,因此所有更新通常都会形成自己的事务。

但是,在这种情况下,有一个工具2,它使用工具1,并将其所有操作包装在自己的事务中。因此,这个单一更新毕竟是更大事务的一部分(包含同一表中另一行的更新)。

这使得它更清晰地解释为什么会发生死锁 - 正在实施的修复是按照主键的顺序进行这两个更新(在这种类型的死锁here上有一个很好的帖子)。

我也会考虑@fancyPants建议的一些模式更改,虽然这需要更多的工作 - 我提供的示例相当简单,表格设计总体上非常可怕。它可以肯定使用一些sprucing ...

1

这是因为你没有使用你的INSERT ... SELECT查询的良好指标。

你有一个索引type,但这是(我猜)完全没用。此列只能有3个不同的值。你的查询甚至要求2中的3个。索引越好,选择性越高。换句话说,这个距离越近,

SELECT COUNT(DISTINCT columnname)/COUNT(*) FROM yourtable; 

是1,最好是你的指数。

这里使用索引太贵,因为实际上它需要MySQL首先读取索引,然后再读取实际数据。因此,阅读整个表格会更便宜。

对于您的情况,source的索引会更好。或者甚至是(source, type)的综合指数。 (顺序在这里很重要,首先选择性更强!)

+0

您能否阐明如何让不同的索引避免死锁? – rbennett485

+0

它会避免死锁,因为你的查询根本不使用索引。因此InnoDB在整个​​表上使用行级锁。查看最后一条语句[here](http://dev.mysql.com/doc/refman/5.7/en/insert-select.html)。你也可以在你的'show engine innodb status'输出中看到你的查询持有9395927行锁。 – fancyPants

+0

谢谢你。我仍然不明白为什么整个表的行级锁应该导致死锁,它应该只是举行更新查询 - 你能够解释什么更新查询正在做的锁定,允许一个发生死锁?这是我真的在这个问题上得到的 – rbennett485