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_fieldid的值似乎并不重要

此单个更新与 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 整体设计相当可怕.它肯定需要一些修饰...