MySQL死锁与存储过程生成UID

时间:2012-07-05 14:19:32

标签: mysql stored-procedures innodb deadlock

我有一个存储过程从“票证”表生成UID,但在负载下我遇到了很多死锁。每当我的任务需要一个新的UID时,我就会从多个并发连接中多次调用此过程。

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    |                |
+-------+---------------------+------+-----+---------+----------------+

我已经设置了read-committed事务隔离:

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)

如果有人能够解释正在发生的事情以及如何避免这些问题,我将不胜感激。

3 个答案:

答案 0 :(得分:2)

这样做:

CREATE TABLE tickets
(
    uid serial
)

然后得到下一个uid:

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

uid serial相当于

uid BIGINT(20) UNSIGNED NOT NULL PRIMARY KEY auto_increment

您不应该遇到任何使用此方法的死锁,并且可以根据需要添加任意数量的连接。

答案 1 :(得分: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。

答案 2 :(得分: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,则不会锁定表(实际更新语句除外)。

相关问题