今天在生产环境上发现了一个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
表将uid
和gid
做了联合主键,仅仅是为了防止重复。而如果同一个用户多次触发这一个存储过程,问题就出现了。
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一下。
尽量避免使用存储过程!!!
尽量避免使用存储过程!!!
尽量避免使用存储过程!!!