【问题标题】:MySQL deadlocks with stored procedure generating UIDMySQL 死锁与存储过程生成 UID
【发布时间】:2012-07-05 23:18:33
【问题描述】:

我有一个从“票证”表生成 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    |                |
+-------+---------------------+------+-----+---------+----------------+

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

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)

如果有人能解释发生了什么以及如何避免它们,我将不胜感激。

【问题讨论】:

  • 供参考:即使使用这个简单的序列也会发生死锁:START TRANSACTION; SELECT uid FROM uid_data FOR UPDATE; UPDATE uid_data SET uid = uid +1 [[possible deadlock here]] ; COMMIT;(因此它与ON DUPLICATE 子句无关)。但是,隔离级别为REPEATABLE READ; 时不会发生死锁。我仍然不知道从这一点上得出什么结论。

标签: mysql stored-procedures innodb deadlock


【解决方案1】:

在这种情况下会发生死锁:

事务 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。

【讨论】:

  • 修改后的存储过程是否可以处理任何类型的锁定并发?
  • @Sencha 是的,UPDATE是原子的,它还会锁定行直到事务结束。但是,我仍然对您原始序列中死锁的原因感到非常好奇(另请参阅我的 cmets 对您的问题)。
【解决方案2】:

你可以试试

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 则不会锁定表(实际更新语句期间除外)。

【讨论】:

    【解决方案3】:

    这样做:

    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
    

    您不应该在使用这种方法时遇到任何死锁,并且可以向其抛出任意数量的连接。

    【讨论】:

    • 为了清楚起见,我应该补充一点,LAST_INSERT_ID() 是范围受限的——例如如果其中 1000 个查询同时运行,则永远不会有为不同连接获取错误编号的风险。
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2014-11-24
    • 1970-01-01
    • 2011-10-22
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多