使用MySQL存储过程时要记得异常处理

今天在生产环境上发现了一个BUG,在网络不佳的情况下,用户可以无限领取奖励,即使系统返回的Toast是领取失败,但用户确实领取到了;其实在功能设计之初,我就考虑了重复领取的问题,我采用存储过程去实现这一领取动作。举个例子:

label_main:
BEGIN
    DECLARE insert_count INT DEFAULT 0;
    START TRANSACTION;
    #更新库存
    UPDATE `tb_goods` SET `surplus` = `surplus` - 1 WHERE `surplus` > 0 AND `gid` = good_id;

    #检查是否更新成功
    SELECT ROW_COUNT() INTO insert_count;
    IF (insert_count = 0) THEN
        ROLLBACK;
        SET result = -1;
        LEAVE label_main;
    ELSEIF (insert_count < 0) THEN
        ROLLBACK;
        SET result = -2;
        LEAVE label_main;
    END IF;

    #做记录
    INSERT INTO `tb_record` (`uid`,`gid`,`ctime`) VALUES (user_id,good_id,CURRENT_TIMESTAMP);

    #检查是否插入成功
    SELECT ROW_COUNT() INTO insert_count;

    IF (insert_count = 0) THEN ROLLBACK;
        SET result = -3;
        LEAVE label_main;
    ELSEIF (insert_count < 0) THEN
        ROLLBACK;
        SET result = -4;
        LEAVE label_main;
    END IF;

    COMMIT;
    SET result = 1;
END

一眼看过去似乎没有问题,但是tb_record表将uidgid做了联合主键,仅仅是为了防止重复。而如果同一个用户多次触发这一个存储过程,问题就出现了。

tb_goods表的库存会不断地被扣除,但是tb_record却没有记录下是谁扣地库存。

在INSERT插入记录时,会提示
1062 - Duplicate entry '1-1' for key 'PRIMARY'

进而导致INSERT之后地SQL语句没有被执行,ROLLBACK语句没有执行。

解决这个问题其实很容易,我们只需要在事务开始前声明以下异常处理即可

label_main:
BEGIN
    DECLARE insert_count INT DEFAULT 0;
    DECLARE CONTINUE HANDLER FOR SQLEXCEPTION SET insert_count=-1;
    START TRANSACTION;
    #...

我记得这个坑我以前是踩过地,再mark一下。

尽量避免使用存储过程!!!

尽量避免使用存储过程!!!

尽量避免使用存储过程!!!

评论