高並發訪問mysql時的問題(一):庫存超減

來源:互聯網
上載者:User

標籤:

如果在對某行記錄的更新時不採取任何防範措施,在多線程訪問時,就容易出現庫存為負數的錯誤.

以下用php、mysql,apache ab工具舉例說明:

mysql表結構

CREATE TABLE `yxt_test_concurrence` (  `id` int(11) NOT NULL AUTO_INCREMENT,  `value` int(11) NOT NULL COMMENT ‘庫存‘,  PRIMARY KEY (`id`)) ENGINE=InnoDB AUTO_INCREMENT=2 DEFAULT CHARSET=utf8 COLLATE=utf8_unicode_ci COMMENT=‘庫存表‘;
CREATE TABLE `yxt_test_pv` (  `id` int(10) unsigned NOT NULL AUTO_INCREMENT,  `val` int(255) DEFAULT NULL COMMENT ‘該線程讀取到的庫存數量‘,  PRIMARY KEY (`id`)) ENGINE=MyISAM AUTO_INCREMENT=351 DEFAULT CHARSET=utf8 COLLATE=utf8_unicode_ci COMMENT=‘訪問記錄表,每次訪問都增加一條記錄,並記錄此次訪問時的庫存數‘;

在庫存表中存入類比庫存500個.

在此,為方便,php採用TP架構:

public function tc(){        $this->tc = M("test_concurrence");//類比商品的剩餘數量        $this->pv = M("test_pv");//類比訪問次數        $res=$this->tc->field(‘value‘)->find(1);//查到的剩餘數量        $value=$res[‘value‘];        if($value>0){//如果大於0,則進行下面的邏輯        $this->pv->data(array(‘val‘=>$value))->add();//這個是用來記錄訪問的次數,並記錄此次訪問時的庫存數
        M()->execute("UPDATE `yxt_test_concurrence` SET `value`=`value` - 1 WHERE `id` = 1"); //商品數量減1
}
}

使用ab工具類比並發訪問:

C:\Users\chenhui>ab -c 50 -n 500 http://study.com/course/Course/tc/This is ApacheBench, Version 2.3 <$Revision: 1554214 $>Copyright 1996 Adam Twiss, Zeus Technology Ltd, http://www.zeustech.net/Licensed to The Apache Software Foundation, http://www.apache.org/Benchmarking studyyxtcmf.com (be patient)Completed 100 requestsCompleted 200 requestsCompleted 300 requestsCompleted 400 requestsCompleted 500 requestsFinished 500 requestsServer Software:        Apache/2.4.9Server Hostname:        studyyxtcmf.comServer Port:            80Document Path:          /course/Course/tc/Document Length:        25786 bytesConcurrency Level:      50Time taken for tests:   60.035 secondsComplete requests:      500Failed requests:        450   (Connect: 0, Receive: 0, Length: 450, Exceptions: 0)Total transferred:      12973630 bytesHTML transferred:       12785130 bytesRequests per second:    8.33 [#/sec] (mean)Time per request:       6003.543 [ms] (mean)Time per request:       120.071 [ms] (mean, across all concurrent requests)Transfer rate:          211.03 [Kbytes/sec] receivedConnection Times (ms)              min  mean[+/-sd] median   maxConnect:        0    1   2.1      1      34Processing:   781 5915 1578.6   5996   12272Waiting:      765 5901 1581.8   5983   12261Total:        783 5916 1578.4   5997   12272Percentage of the requests served within a certain time (ms)  50%   5997  66%   6385  75%   6707  80%   6850  90%   7387  95%   8402  98%   9734  99%  10300 100%  12272 (longest request)

查看資料庫記錄:

SELECT * from yxt_test_pv;--截取一段記錄(左邊是第幾次訪問,右側是當次訪問看到的庫存)| 338 |  164 || 339 |  164 || 340 |  163 || 341 |  162 || 342 |  162 || 343 |  162 || 344 |  162 || 345 |  157 || 346 |  156 || 347 |  156 || 348 |  153 || 349 |  155 || 350 |  151 |

可以發現在341-343次讀取的庫存數量是一樣的,在庫存還很多的情況時,並不會出現問題:因為程式中減庫存的邏輯,是當前庫存量減去1.但是庫存不多的時候,就很可能出現問題,比如庫存只有一個了,而此時有多個線程查詢到此時還有一個庫存,因為1>0滿足條件,所以庫存減1,多個線程都對當前庫存減1,最後就多減了庫存,出現負數,這是不允許的.

所以一定要採取措施.

我認為,總的原則是:對於某一個時刻的庫存,只允許一個會話去修改.要滿足此條件.有兩種選擇:

1.對於某一個時刻的庫存,只允許一個會話去讀取(鎖機制).待鎖被釋放後,其他會話才可以讀取庫存.

2.對於某一個時刻的庫存,設定版本(即增加一個版本欄位,用於比較.我對版本的理解是刻個記號),更新庫存時要判斷版本是否發生變化,若沒發生變化,則更新庫存的同時,更新版本號碼.若更新庫存時發現版本發生變化了,那一定是有別的線程早已對庫存修改,此情況下就放棄修改.

選擇1.使用mysql的鎖機制.(悲觀鎖)

 

public function tc(){        $this->tc = M("test_concurrence");//類比商品的剩餘數量        $this->pv = M("test_pv");//類比訪問次數        //對錶加鎖,注意,如果加鎖過程中要操作多個表,要對這幾個表都加鎖,否則會報錯
    //mysql> lock table yxt_test_concurrence read;--只鎖了一張表

         //Query OK, 0 rows affected (0.00 sec)

         //mysql> SELECT * from yxt_test_pv;--讀取沒有被鎖的表

        //ERROR 1100 (HY000): Table ‘yxt_test_pv‘ was not locked with LOCK TABLES--報錯,提示查詢的表沒有被鎖住

        M()->execute("lock tables yxt_test_concurrence write,yxt_test_pv write;");        $res=$this->tc->field(‘value‘)->find(1);//查到的剩餘數量        $value=$res[‘value‘];        if($value>0){//如果大於0,則進行下面的邏輯            $this->pv->data(array(‘val‘=>$value))->add();//這個是用來記錄訪問的次數            M()->execute(            "UPDATE `yxt_test_concurrence` SET `value`=`value` - 1 WHERE `id` = 1");            //商品數量減1        }        //解鎖        M()->execute("unlock tables");    }

採用鎖機制,可以嚴格控制庫存數量的變化,但是採用鎖會增加資料庫的開銷. 

選擇2.版本控制(樂觀鎖)

樂觀鎖,是假定事務之間是互不干擾的,事務在訪問資料的時候,並不會擷取鎖,但是,在提交前,每個事務都要確保其他事務並沒有修改他讀取到的資料.如果在更新資料時發現其他事務已經修改了資料,則復原提交.樂觀鎖經常用於"低爭用資料結構"的情境中.當衝突特別少的時候,事務可以在完成時,不需要管理鎖的開銷及等待其他事務釋放鎖,這可以帶來更高的吞吐率.但是,如果對於資料的爭用特別頻繁,重新開啟一個新事務的開銷會明顯影響效能.

通常認為,其他並發控制方法,在此情況下會有更好的表現,然而,基於悲觀鎖的方法,會導致較差的效能.因為即使死結可以避免,"鎖"仍會極大的影響並發效能.(我想應該是因為會話被阻塞,從而導致只能串列訪問資料庫)

以上定義摘自wiki:https://en.wikipedia.org/wiki/Optimistic_concurrency_control

這種情況下,如果並發訪問,則修改失敗的幾率會較高,

舉例:在熱銷產品情境下則容易出現購買失敗的情況.這對使用者的體驗是不好的.因為這意味著又要重新嘗試一次.

 

小結:應該採取哪一種鎖,應根據實際情境來權衡利弊,如果更新的很頻繁,那應該使用悲觀鎖.此刻需要考慮的問題是:如何解決並發問題.如果很少更新,則使用樂觀鎖更為方便省事.

高並發訪問mysql時的問題(一):庫存超減

聯繫我們

該頁面正文內容均來源於網絡整理,並不代表阿里雲官方的觀點,該頁面所提到的產品和服務也與阿里云無關,如果該頁面內容對您造成了困擾,歡迎寫郵件給我們,收到郵件我們將在5個工作日內處理。

如果您發現本社區中有涉嫌抄襲的內容,歡迎發送郵件至: info-contact@alibabacloud.com 進行舉報並提供相關證據,工作人員會在 5 個工作天內聯絡您,一經查實,本站將立刻刪除涉嫌侵權內容。

A Free Trial That Lets You Build Big!

Start building with 50+ products and up to 12 months usage for Elastic Compute Service

  • Sales Support

    1 on 1 presale consultation

  • After-Sales Support

    24/7 Technical Support 6 Free Tickets per Quarter Faster Response

  • Alibaba Cloud offers highly flexible support services tailored to meet your exact needs.