1. 程式人生 > >項目中遇到的超賣問題及解決辦法(使用go做測試工具)

項目中遇到的超賣問題及解決辦法(使用go做測試工具)

mysq etime engine fat 前端 ray -s false 案例

  超賣問題:在一個很短的時間內,Mysql的數據狀態在 取出,比較,提交,或修改中,另外一個進程訪問數據導致的超賣問題。

  案例:

    1.前端沒有做限制,如果用戶連續點擊簽到,那麽會有多條數據發送到後端,如果數據狀態沒有來得及完全修改過來,導致用戶的簽到數據被多次添加。

    2.每天簽到用戶的前3名用戶可以獲得一張價值100元的優惠券,如果有多名用戶在很短的時間內同時簽到,那麽就會有多發的問題。

  

解決案例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;

    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); } }

正常情況下,數據會正確的添加到數據庫,且不會被多次添加,但是使用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
	}
}

  運行Go測試代碼之後,數據被重復插入到數據庫

    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)
                ->lock(true)
                ->find();
        if(!$sign_value){
            $add_value = array(
                ‘member_id‘    =>$member_id,
                ‘sign_date‘    =>$sign_date
            );
            //添加一條簽到記錄
            M(‘concurrency‘,‘crm_‘)->add($add_value);            
        }
    }

加鎖之後,就不會出現問題了。加上lock(true)的實際就是在查詢語句最後加上 for update

項目中遇到的超賣問題及解決辦法(使用go做測試工具)