zoukankan      html  css  js  c++  java
  • 项目中遇到的超卖问题及解决办法(使用go做测试工具)

      超卖问题:在一个很短的时间内,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); }
         
         M('')->commit(); }

    //SELECT * FROM `crm_concurrency` WHERE `member_id` = 1 AND `sign_date`
    //= '2017-12-25' LIMIT 1 FOR UPDATE

    二 .使用行锁,解决案例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){
                //申请到了优惠券
            }
        }

    //UPDATE `crm_coupons_config` SET `has_given`=has_given+1 WHERE ( config_count>has_given )

    三 总结

      主要使用到了数据库中的表锁和行锁

  • 相关阅读:
    sql函数
    sql日期
    Windows下串口编程
    Libreoffice/Office:禁止首字母自动大写功能
    convert:图片转pdf失败
    LibreOffice/Calc:单元格设置下拉菜单
    Ubuntu:查询计算机软硬件信息
    tar:文件打包归档
    中科大自主招生2018年笔试数学之五
    文件分割与合并
  • 原文地址:https://www.cnblogs.com/alin-qu/p/8111220.html
Copyright © 2011-2022 走看看