Innodb更新锁定
Innodb update locking
我一直在处理特定的更新查询时遇到各种麻烦,这看起来应该没有问题。我更改了名称,但 table 是:
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_field
和id
的值似乎并不重要
此单个更新与 problem_table
上的各种 select 查询反复死锁,所以我的问题是 - 这个简单的更新查询到底取出了什么锁?我应该补充一点,两个死锁事务都只包含一个查询。
我已经通读了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 ldsn=sym@symprod
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)
发生这种情况是因为您没有用于插入...select 查询的良好索引。
您在 type
上有一个索引,但那(我猜)完全没用。此列只能有 3 个不同的值。您的查询甚至要求 3 个中的 2 个。索引越 selective 就越好。也就是说,越接近this
SELECT COUNT(DISTINCT columnname) / COUNT(*) FROM yourtable;
等于1,你的指数越好。
这里索引的使用成本太高,因为它实际上需要MySQL先读取索引,然后再读取实际数据。因此,阅读整个 table.
更便宜
对于您的情况,source
上的索引会更好。甚至 (source, type)
上的组合索引。 (这里顺序很重要,越select先来一个!)
为了回答埋在中间的基本问题,
取出的锁
UPDATE problem_table SET problem_field = 20000101 WHERE id = 6526153;
问题中 problem_table
的定义是:
事实证明事情并不完全像我描述的那样 - 执行有问题的更新的工具 1 没有事务管理,因此它的所有更新通常都形成自己的事务。
但是,在这种情况下,有一个工具 2,它使用工具 1,并将其所有操作包装在自己的事务中。因此,此单个更新毕竟是更大事务的一部分(包含同一 table 中另一行的更新)。
这使得为什么会发生死锁变得更加清楚 - 正在实施的修复是按主键的顺序进行这两个更新(这种死锁有一个很好的 post here).
我也会考虑按照@fancyPants 的建议进行一些模式更改,尽管这需要更多的工作——我提供的示例非常精简,table 整体设计相当可怕.它肯定需要一些修饰...
我一直在处理特定的更新查询时遇到各种麻烦,这看起来应该没有问题。我更改了名称,但 table 是:
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_field
和id
的值似乎并不重要
此单个更新与 problem_table
上的各种 select 查询反复死锁,所以我的问题是 - 这个简单的更新查询到底取出了什么锁?我应该补充一点,两个死锁事务都只包含一个查询。
我已经通读了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 ldsn=sym@symprod
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)
发生这种情况是因为您没有用于插入...select 查询的良好索引。
您在 type
上有一个索引,但那(我猜)完全没用。此列只能有 3 个不同的值。您的查询甚至要求 3 个中的 2 个。索引越 selective 就越好。也就是说,越接近this
SELECT COUNT(DISTINCT columnname) / COUNT(*) FROM yourtable;
等于1,你的指数越好。
这里索引的使用成本太高,因为它实际上需要MySQL先读取索引,然后再读取实际数据。因此,阅读整个 table.
更便宜对于您的情况,source
上的索引会更好。甚至 (source, type)
上的组合索引。 (这里顺序很重要,越select先来一个!)
为了回答埋在中间的基本问题,
取出的锁 UPDATE problem_table SET problem_field = 20000101 WHERE id = 6526153;
问题中 problem_table
的定义是:
事实证明事情并不完全像我描述的那样 - 执行有问题的更新的工具 1 没有事务管理,因此它的所有更新通常都形成自己的事务。
但是,在这种情况下,有一个工具 2,它使用工具 1,并将其所有操作包装在自己的事务中。因此,此单个更新毕竟是更大事务的一部分(包含同一 table 中另一行的更新)。
这使得为什么会发生死锁变得更加清楚 - 正在实施的修复是按主键的顺序进行这两个更新(这种死锁有一个很好的 post here).
我也会考虑按照@fancyPants 的建议进行一些模式更改,尽管这需要更多的工作——我提供的示例非常精简,table 整体设计相当可怕.它肯定需要一些修饰...