在线时间:8:00-16:00
迪恩网络APP
随时随地掌握行业动态
扫描二维码
关注迪恩网络微信公众号
超卖问题:在一个很短的时间内,Mysql的数据状态在 取出,比较,提交,或修改中,另外一个进程访问数据导致的超卖问题。 案例: 1.前端没有做限制,如果用户连续点击签到,那么会有多条数据发送到后端,如果数据状态没有来得及完全修改过来,导致用户的签到数据被多次添加。 2.每天签到用户的前3名用户可以获得一张价值100元的优惠券,如果有多名用户在很短的时间内同时签到,那么就会有多发的问题。
一 .使用表锁,解决案例1中的问题 1.1 新建一张用户签到表 DROP TABLE IF EXISTS `crm_concurrency`; CREATE TABLE `crm_concurrency` ( `id` INT (10) UNSIGNED NOT NULL AUTO_INCREMENT, `member_id` INT (10) UNSIGNED DEFAULT NULL, -- 会员ID `sign_date` date DEFAULT NULL, -- 签到日期 `create_at` datetime DEFAULT NULL, PRIMARY KEY (`id`) ) ENGINE = INNODB DEFAULT CHARSET = utf8; 1.2 添加签到记录逻辑 public function addSignValue(){ //会员ID $member_id = I('get.member_id'); if(!$member_id){ return false; } //签到日期 $sign_date = date('Y-m-d'); $where_condition = array( 'member_id' =>$member_id, 'sign_date' =>date('Y-m-d') ); //查询用户是否已经签到过 $sign_value = M('concurrency','crm_') ->where($where_condition)->find(); if(!$sign_value){ $add_value = array( 'member_id' =>$member_id, 'sign_date' =>$sign_date ); //给未签到用户 添加一条签到记录 M('concurrency','crm_')->add($add_value); } } 1.3 Go压力测试代码 func fGet(url string) { res, err := http.Get(url) defer res.Body.Close() if err != nil { log.Fatal(err) } re, err := ioutil.ReadAll(res.Body) if err != nil { log.Fatal(err) } fmt.Println(string(re)) ch <- struct{}{} } var ch = make(chan struct{}) func main() { url := "测试URL?member_id="; for index := 1; index <= 10; index++ { tmp := url + strconv.Itoa(1) ; go fGet(tmp) } for index := 1; index <= 10; index++ { <-ch } } 1.4 加上lock(true)可以解决问题。实际就是在查询语句最后加上 for update,这里用到了表锁。 public function addSignValue(){//锁表 M('')->startTrans(); $sign_value = M('concurrency','crm_') ->where($where_condition) ->lock(true) ->find(); if(!$sign_value){ $add_value = array( 'member_id' =>$member_id, 'sign_date' =>$sign_date ); //添加一条签到记录 M('concurrency','crm_')->add($add_value); }
二 .使用行锁,解决案例2中的问题 1.新建一张签到发券配置表和发券表,这里只建了一个配置表做演示 DROP TABLE IF EXISTS `crm_coupons_config`; CREATE TABLE `crm_coupons_config` ( `id` int(10) unsigned NOT NULL AUTO_INCREMENT, `config_count` int(10) unsigned DEFAULT NULL COMMENT '要发放券的数量', `has_given` int(255) unsigned DEFAULT NULL COMMENT '已发放的券的数量', `create_at` datetime DEFAULT NULL, PRIMARY KEY (`id`) ) ENGINE=InnoDB DEFAULT CHARSET=utf8; 2.下面是第一种思路的伪代码,也使用了表锁 public function sendCoupons(){ //1.从crm_coupons_config表中取出配置 //2.锁住发券表,并计算已经发的券的数量 //3.如果已发的券的数量大于等于配置表的数量,则停止发送券 } 3.下面是另外一一种思路,使用到了行锁。不需要添加额外锁表代码,因为mysql在查询,更新 的时候,是默认锁表的 public function sendCoupons(){ $req = M('coupons_config','crm_') ->where('config_count>has_given') ->setInc('has_given'); if($req){ //申请到了优惠券 } }
三 总结 主要使用到了数据库中的表锁和行锁 |
请发表评论