MySQL死锁与存储过程生成UID

Sen*_*cha 6 mysql deadlock innodb stored-procedures

我有一个存储过程从"票证"表生成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
Run Code Online (Sandbox Code Playgroud)

我考虑过使用:

BEGIN
    REPLACE INTO uid_data (stub) VALUES ('a');
    SELECT LAST_INSERT_ID();
END
Run Code Online (Sandbox Code Playgroud)

但是我不确定这对于并发连接是否安全,因为没有锁定,不像第一个程序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    |                |
+-------+---------------------+------+-----+---------+----------------+
Run Code Online (Sandbox Code Playgroud)

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

mysql> SHOW VARIABLES LIKE 'tx_isolation';
+---------------+-----------------+
| Variable_name | Value           |
+---------------+-----------------+
| tx_isolation  | READ-COMMITTED  |
+---------------+-----------------+
Run Code Online (Sandbox Code Playgroud)

这是我要回来的 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)
Run Code Online (Sandbox Code Playgroud)

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

Ran*_*eed 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
Run Code Online (Sandbox Code Playgroud)

那这个呢:

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
Run Code Online (Sandbox Code Playgroud)

你可以stub完全放弃你的专栏。唯一的缺点是您必须用一行初始化您的 uid_data 。