MySQL中的死锁

时间:2014-05-28 21:27:35

标签: mysql

请原谅一些新手问题 - 我是一名试图担任DBA角色的开发人员,此刻还有点夸张。

我目前正在尝试解决的主要问题是MySQL向我展示了很多死锁错误。我们已经运行了几个月没有任何东西,在添加了我们认为相当无害的变化之后,我们突然间会看到一个小时十几个小时。

我们看到的具体错误(表/列名称被擦除,我们正在使用您在那里看到的一些JDBI):

------------------------
LATEST DETECTED DEADLOCK
------------------------
2014-05-21 19:11:13 2b4c3602a700
*** (1) TRANSACTION:
TRANSACTION 1327203423, ACTIVE 0 sec starting index read
mysql tables in use 3, locked 3
LOCK WAIT 4 lock struct(s), heap size 1248, 3 row lock(s)
MySQL thread id 6182129, OS thread handle 0x2b4c36683700, query id 2061701269 [host] [ip] [user] updating
/* WriteDAO.update */ UPDATE A        SET t = 0, createdDate = '2014-05-21 19:11:13'        WHERE uid = 1 AND tid = 100
*** (1) WAITING FOR THIS LOCK TO BE GRANTED:
RECORD LOCKS space id 109 page no 84 n bits 584 index `idx_A_tid` of table `core`.`A` trx id 1327203423 lock_mode X waiting
Record lock, heap no 167 PHYSICAL RECORD: n_fields 2; compact format; info bits 0
 0: len 8; hex 8000000000002740; asc       '@;;
 1: len 8; hex 8000000000002747; asc       'G;;

*** (2) TRANSACTION:
TRANSACTION 1327203438, ACTIVE 0 sec fetching rows
mysql tables in use 3, locked 3
6 lock struct(s), heap size 1248, 6 row lock(s)
MySQL thread id 6182028, OS thread handle 0x2b4c3602a700, query id 2061701279 [host] [ip] [user] updating
/* WriteDAO.update */ UPDATE A        SET t = 0, createdDate = '2014-05-21 19:11:13'        WHERE uid = 2 AND tid = 100
*** (2) HOLDS THE LOCK(S):
RECORD LOCKS space id 109 page no 84 n bits 584 index `idx_A_tid` of table `core`.`A` trx id 1327203438 lock_mode X
Record lock, heap no 167 PHYSICAL RECORD: n_fields 2; compact format; info bits 0
 0: len 8; hex 8000000000002740; asc       '@;;
 1: len 8; hex 8000000000002747; asc       'G;;

Record lock, heap no 168 PHYSICAL RECORD: n_fields 2; compact format; info bits 0
 0: len 8; hex 8000000000002740; asc       '@;;
 1: len 8; hex 800000000000274e; asc       'N;;

*** (2) WAITING FOR THIS LOCK TO BE GRANTED:
RECORD LOCKS space id 109 page no 6 n bits 344 index `PRIMARY` of table `core`.`A` trx id 1327203438 lock_mode X locks rec but not gap waiting
Record lock, heap no 63 PHYSICAL RECORD: n_fields 7; compact format; info bits 0
 0: len 8; hex 800000000000274e; asc       'N;;
 1: len 6; hex 00004f13d872; asc   O  r;;
 2: len 7; hex 42000001e02f43; asc B    /C;;
 3: len 8; hex 8000000000002771; asc       'q;;
 4: len 8; hex 8000000000002740; asc       '@;;
 5: len 8; hex 8000000000000000; asc         ;;
 6: len 5; hex 9992eb085c; asc     \;;

*** WE ROLL BACK TRANSACTION (1)

表A非常简单,最后有两个索引:

CREATE TABLE A (
  id          BIGINT NOT NULL AUTO_INCREMENT,
  uid         BIGINT NOT NULL,
  tid         BIGINT NOT NULL,
  t           BIGINT NOT NULL,
  createdDate DATETIME NOT NULL,
  PRIMARY KEY (id)
) ENGINE=InnoDB AUTO_INCREMENT=10001 DEFAULT CHARACTER SET=utf8 COLLATE=utf8_unicode_ci;

CREATE INDEX idx_A_uid ON A (uid);
CREATE INDEX idx_A_tid ON A (tid);

我们没有在事务中针对这些表运行任何东西,除了JDBI事务通过@SqlUpdate / @SqlQuery / etc注释自动执行的操作,例如:

@SqlUpdate("UPDATE A "+
           "       SET t = :t, createdDate = :createdDate "+
           "       WHERE uid = :uid AND tid = :tid")
public int update (@BindBean T t);

所有这些查询都运行得非常快(慢查询日志不会显示任何超过一秒的内容,而且几乎所有查询都比这少得多)。我们还没有那么重要的负载。

第一个问题:

mysql tables in use 3, locked 3

为什么MySQL锁定3个表来对其进行更新?是否在此输出中将索引视为表?

第二个问题:

RECORD LOCKS space id 109 page no 6 n bits 344 index `PRIMARY` of table `core`.`A` trx id 1327203438 lock_mode X locks rec but not gap waiting

为什么A的主键被锁定?除了初始行创建之外,我们不会触及该列> at all<请注意,update语句都没有触及该列,它们甚至应该针对不同的行进行操作。我们还使用分布式hazelcast来获取分布式锁,因此一次只有一个线程可以尝试访问给定行以便在该表中写入。一行读取锁定会导致写入死锁吗?即使它可以,行级锁定不应该解决这个问题吗?

第三个问题:如果MySQL需要更新索引中的一行,它是否像表锁一样锁定整个索引?或者它是否像表索引那样进行行级锁定?

有关调试的建议吗?

由于

1 个答案:

答案 0 :(得分:1)

我总是建议您不要在where子句中使用UPDATE运行PRIMARY KEY语句。

您有2笔交易: -

TX1:

UPDATE A
SET t = 0, createdDate = '2014-05-21 19:11:13'        
WHERE uid = 1 AND tid = 100

TX2:

UPDATE A        
SET t = 0, createdDate = '2014-05-21 19:11:13'        
WHERE uid = 2 AND tid = 100

两个TX都在不使用Primary Key进行更新。

所以让我们说例如TX2中的查询必须更新20行。所以它的作用是在整个A表上放置一个S锁(因为它不知道id' s)然后它会一直一个地在20行中放置一个X锁,因为它会不断更新它们。

说TX2正在更新第13行,此时TX1会尝试更新它,因此需要进行X锁定。所以它放入了对X锁的请求并进入队列。 MySQL中的队列是FIFO(先进先出)。因此,在TX1发出X锁定请求后,TX2完成更新第13行,现在请求X锁定以更新第14行,但它无法做到这一点,因为TX2无法获得其等待的X锁定TX1获得X锁定。并且TX1无法获得其X锁定,直到TX2释放其S锁定。 这就是你遇到僵局的原因。 我希望所有这些都是有道理的......我强烈建议你阅读这篇文章 - http://dev.mysql.com/doc/refman/5.0/en/innodb-lock-modes.html

需要注意的重点是: -

共享(S)锁允许事务读取行。

独占(X)锁允许事务更新或删除行。

如果事务T1在行r上持有共享(S)锁,则来自某个不同事务T2的对行r上的锁的请求按如下方式处理:

可以立即授予T2对S锁的请求。结果,T1和T2都在r上保持S锁定。

不能立即授予T2关于X锁的请求。

解决方案:

因此,解决方案是使用主键进行更新,以便将锁定放在您想要更新的行而不是表格上。

TX2应该是(在PHP中) - 您可以将逻辑转换为您想要的任何语言:

$db = Zend_Db_Table::getDefaultAdapter();
$select = "SELECT id From A where uid = 2 AND tid = 100";
$rows = $db->fetchAll($select);

foreach ($rows as $row) {
    $id = $row['id'];
    $update = "UPDATE A SET t=0,createdDate = '2014-05-21 19:11:13' Where id = ".$id;
    $db->query($update);
}

因此,您应该选择ID并使用这些ID进行更新,因此只会锁定行而不是整个表。 你应该对TX1做同样的事情。对于流量较高的表格,这种方法很重要。