2012-07-05 30 views
6

我有一个存储过程从“票据”表生成UID的,但在负载下我得到了很多的死锁。无论何时我的任务需要新的UID,我都会从多个并发连接多次调用此过程。存储过程生成UID的MySQL死锁

BEGIN 
    DECLARE a_uid BIGINT(20) UNSIGNED; 
    START TRANSACTION; 
    SELECT uid INTO a_uid FROM uid_data FOR UPDATE; # Lock 
    INSERT INTO uid_data (stub) VALUES ('a') ON DUPLICATE KEY UPDATE uid=uid+1; 
    SELECT a_uid+1 AS `uid`; 
    COMMIT; 
END 

我没有考虑使用:

BEGIN 
    REPLACE INTO uid_data (stub) VALUES ('a'); 
    SELECT LAST_INSERT_ID(); 
END 

不过,我不知道,如果这将是与并发连接的安全,因为没有锁定,不像与SELECT FOR UPDATE的第一个过程。

这里的表:

mysql> DESCRIBE uid_data; 
+-------+---------------------+------+-----+---------+----------------+ 
| Field | Type    | Null | Key | Default | Extra   | 
+-------+---------------------+------+-----+---------+----------------+ 
| uid | bigint(20) unsigned | NO | PRI | NULL | auto_increment | 
| stub | char(1)    | NO | UNI | NULL |    | 
+-------+---------------------+------+-----+---------+----------------+ 

我已经为读提交的事务隔离设置:

mysql> SHOW VARIABLES LIKE 'tx_isolation'; 
+---------------+-----------------+ 
| Variable_name | Value   | 
+---------------+-----------------+ 
| tx_isolation | READ-COMMITTED | 
+---------------+-----------------+ 

这里就是我从SHOW ENGINE INNODB STATUS;

... 
... dozens and dozens of the following record locks... 

Record lock, heap no 1046 PHYSICAL RECORD: n_fields 2; compact format; info bits 32 
0: len 1; hex 61; asc a;; 
1: len 8; hex 00000000000335f2; asc  5 ;; 

Record lock, heap no 1047 PHYSICAL RECORD: n_fields 2; compact format; info bits 32 
0: len 1; hex 61; asc a;; 
1: len 8; hex 00000000000335f1; asc  5 ;; 

*** (2) WAITING FOR THIS LOCK TO BE GRANTED: 
RECORD LOCKS space id 13 page no 4 n bits 1120 index `stub` of table `my_db`.`uid_data` trx id 13AA89 lock_mode X waiting 
Record lock, heap no 583 PHYSICAL RECORD: n_fields 2; compact format; info bits 32 
0: len 1; hex 61; asc a;; 
1: len 8; hex 00000000000334a8; asc  4 ;; 

*** WE ROLL BACK TRANSACTION (1) 

我找回如果有人能够解释发生了什么以及如何避免这些事情,我会很感激。

+0

有关信息:即使使用此简单序列,也会发生死锁:'START TRANSACTION; SELECT uid FROM uid_data FOR UPDATE; UPDATE uid_data SET uid = uid +1 [[此处可能存在死锁]]; COMMIT;'(因此它与'ON DUPLICATE'子句无关)。但是,隔离级别为'REPEATABLE READ;'时不会发生死锁。我仍然不知道从这一点可以得出什么结论。 – RandomSeed 2012-07-06 11:40:11

回答

0

在这种情况下发生死锁:

交易1:请求锁(SELECT...FOR UPDATE)并获取

事务2:请求锁(SELECT...FOR UPDATE),必须等待

事务1:尝试插入,命中重复,因此更新(INSERT...ON DUPLICATE KEY UPDATE)=>死锁

我不太确定重新不久,我怀疑它与 ON DUPLICATE KEY UPDATE有关。如果我发现,我仍在调查,并会回来。

[编辑]死锁发生,即使:

BEGIN 
    START TRANSACTION; 
    SELECT uid FROM uid_data FOR UPDATE; 
    UPDATE uid_data SET uid = uid +1; -- here, a deadlock would be detected in a blocked, concurrent connection 
    COMMIT; 
END 

这个怎么样:

BEGIN 
    START TRANSACTION;  
    UPDATE uid_data SET uid = uid +1; 
    SELECT uid FROM uid_data; 
    COMMIT; 
END 

你可以完全放下你的stub科拉姆。唯一的缺点是你必须用一行初始化你的uid_data。

+0

该修订的存储过程是否处理与任何类型的锁定并发? – Sencha 2012-07-06 10:05:30

+0

@Sencha是的,'UPDATE'是原子的,它也锁定行直到事务结束。但是,我仍然非常好奇原始序列中死锁的原因(另请参阅我对您问题的评论)。 – RandomSeed 2012-07-06 11:42:08

0

您可以尝试在桌子上使用

UPDATE uid_data SET uid = LAST_INSERT_ID(uid+1); 
SELECT LAST_INSERT_ID(); 

CREATE TABLE `uid_data` (
    `uid` BIGINT(20) UNSIGNED NOT NULL 
) 
COLLATE='utf8_general_ci' 
ENGINE=MyISAM; 

这是线程安全的,如果是MyISAM数据(除了实际的更新语句时)将不会锁定表。

2

这样做:

CREATE TABLE tickets 
(
    uid serial 
) 

然后获得下一个UID:

BEGIN 
    INSERT INTO tickets VALUES (NULL); 
    SELECT LAST_INSERT_ID(); 
END 

UID串行相当于

uid BIGINT(20) UNSIGNED NOT NULL PRIMARY KEY auto_increment 

你不应该使用这种方法遇到任何死锁并且可以随心所欲地投入尽可能多的连接。

+0

为清楚起见,我应该添加LAST_INSERT_ID()是受限制的 - 例如,如果这些查询中有1000个是同时运行的,那么不会有为其他连接获取错误号码的风险。 – 2015-01-14 20:38:52