袋鼠玩趣城小程序优惠券重复领取问题分析与PHP后端解决方案
在我的袋鼠玩趣城小程序项目里,当用户进行领取优惠券的操作时,出现了数据重复记录的情况。这意味着原本每人只能领取一张的操作,却有个别用户领到了 2 张或者 3 张。

分析了原因,在网络状态不好时,用户多次点击了领取按钮,从而导致了类似高并发的场景。
解决办法:
从前端进行解决,这种方法能够缓解一部分问题,不过我个人认为无法彻底杜绝。
小程序的前端加上 防抖操作或者做节流操作
2:从后端解决,(php 后端)
方法又有三种
a: 使用php的文件锁
//这里为高并发的情况加上文件销
使用“w+”模式打开名为“./lock.mytxt”的文件,并将其句柄赋值给变量$handle。
如果使用 flock 函数以独占锁(LOCK_EX)的方式锁定了 $handle ,那么就会执行后续的操作。如果没有成功锁定 $handle ,则不会执行后续操作。
$free_ticket_model 获取免费门票,传入参数 $id、$store_id 和当前用户的用户 ID $this->userInfo["uid"],最终结果赋值给 $result
flock($handle, LOCK_UN);
fclose($handle);
if ($result["error"] == 0) {
使用 JsonService 成功返回 "ok"。
} else {
使用 JsonService 的 fail 方法,并传入 result 中的 "msg" 作为参数。
}
}
要记录完成操作后,解锁,并且释放资源
使用 mysql 数据库时可使用类似 select * from table for update 的锁操作。
通过"order_id"来查找与指定"order_id"匹配的 StoreOrderShare 记录,在查找过程中进行锁定,最终找到的记录赋值给$store_order_share 。
这是一个tp5中的加锁的方式
注意,只有和 mysql 的事务一起使用时这个写法才有效。也就是说,上面的这句话必须是在执行 Db::startTrans(); 之后。
说到数据库时,存在一个能解决重复写入问题的方法,即使用数据库的索引,特别是唯一索引。倘若有重复的数据,数据库会直接报错,这样就能解决该问题。
使用 redis 进行解决,通过 setnx 与 setexpire 相结合,以及 watch 和 redis 的事务来达成目的。
伪代码如下
public function testredis(){
$redis = new \Redis();
Redis 连接到了 "127.0.0.1" 这个地址,并且使用的端口是 6379 。
if($redis->setnx("lock",1)){
$redis->expire("lock",5);
$redis->watch("lock");
$redis->multi();
var_dump("做领券的操作");
var_dump("领取完毕,删除这个key");
$redis->del("lock");
$redis->exec();
}else{
使用 JsonService 进行返回操作,返回的结果是失败,具体是领取失败。
}
}
使用 setnx 进行加锁操作,同时设置锁的自动过期时间。利用 watch 来监视这个锁。通过事务,将这些操作组合在一起,使其成为一个原子性的操作。
大至说一下为什么这么做吧
设置过期时间,是因为要避免拿到锁的程序在执行过程中出现中间出错的情况,且如果没有及时解锁,程序就会进入死锁状态。因此,需要让锁自动过期。
为什么又要用事务来操作呢?
上面的锁 5 秒会过期。程序执行完某个操作后会删除锁。如果有一个请求执行了 8 秒,到第 5 秒时锁过期,另一个请求就能抢到锁并开始执行。在第 8 秒时第一个请求会删除锁。此时要考虑它自己的锁已过期,那么它删除的锁是谁的。所以使用了事务来保证其原子性。