在某個(gè)地方看到有個(gè)例子,具體描述類似如下:商店現(xiàn)在某商品只有1件庫(kù)存,然后A與B在網(wǎng)上進(jìn)行下訂,A與B幾乎同時(shí)(或許也就差幾毫秒,A比B快那么一點(diǎn)點(diǎn))進(jìn)行。
很明顯是只有A才能成功下單的,B則會(huì)收到庫(kù)存不足的提示,但是作為放置在服務(wù)端的那個(gè)頁(yè)面(或者稱為腳本程序)我們得怎樣去處理這個(gè)問(wèn)題呢?或者我先放出一段代碼吧。
代碼如下 | 復(fù)制代碼 |
$sql = "select number from goods where id=1"; $number = intval( $db->result( $db->query( $sql ), 0 ) ); if ( $number > 0 ) { sleep( 2 ); $sql = "update goods set number=number-1 where id = 1"; if ( $db->query( $sql ) ) { echo 'Ok!Here you are!'; } else { echo 'Sorry!Something go wrong!Try it again.'; } } else { echo 'No more!you are so late!'; } |
這部分代碼除了缺少一定注釋外都寫(xiě)得沒(méi)錯(cuò),當(dāng)然$db是一個(gè)操作數(shù)據(jù)庫(kù)的類,我只是將大部分方法封裝了,這里的邏輯也是很明顯了。
先獲取id為1這個(gè)東東的庫(kù)存數(shù),看看是否為0,如果為0就訂購(gòu)不成功了,如果大于0則將庫(kù)存減1然后提示ok。這確實(shí)沒(méi)有任何錯(cuò)誤,邏輯也對(duì)。如果請(qǐng)求是一個(gè)接一個(gè)地產(chǎn)生的,那么什么問(wèn)題都沒(méi)有,但當(dāng)一些并發(fā)情況出現(xiàn)時(shí)就可能出現(xiàn)一些無(wú)厘頭的問(wèn)題了。你想啊,是不是可能存在一種情況,A剛發(fā)出請(qǐng)求,腳本處理到update之前B又發(fā)出請(qǐng)求,那么現(xiàn)在庫(kù)存依然還有1,因?yàn)锳的update還沒(méi)有執(zhí)行呢,所以$number不少于0,這次完了,B也下單了,于是庫(kù)存變成-1了(假設(shè)原來(lái)只有1件),確實(shí)是一個(gè)荒謬而且比較搞笑的結(jié)果。
出現(xiàn)問(wèn)題的原因很明顯,就是忽略了這種并發(fā)情況的考慮,處理下訂應(yīng)該是種隊(duì)列方式,也就是先來(lái)先得,就是說(shuō)在執(zhí)行這個(gè)下訂動(dòng)作是要排隊(duì)的,前面的那個(gè)先下訂然后后者才能下訂,當(dāng)然當(dāng)后者下訂前才再判斷庫(kù)存的數(shù)量。那么怎樣解決這個(gè)問(wèn)題呢,在程序?qū)用嫔厦?#20284;真的沒(méi)有方法去解決這個(gè)問(wèn)題,所以在此才提到鎖表的概念,上面出現(xiàn)這個(gè)問(wèn)題的歸根于沒(méi)有控制一個(gè)select number的先后順序(或者可以這么說(shuō)吧),因?yàn)樵贏執(zhí)行update之前你又允許B去查詢庫(kù)存,當(dāng)然結(jié)果還是1,至少要等待A更新庫(kù)存后才允許其他人的任何操作,也就是對(duì)goods表進(jìn)行一個(gè)排隊(duì)操作,對(duì)goods表進(jìn)行鎖定。
說(shuō)到這里,請(qǐng)不要以為鎖表有多么高深,其實(shí)它就是一條sql
LOCK TABLE `table` [READ|WRITE]
解鎖
UNLOCK TABLES;
引用專業(yè)的描述是
LOCK TABLES為當(dāng)前線程鎖定表。 UNLOCK TABLES釋放被當(dāng)前線程持有的任何鎖。當(dāng)線程發(fā)出另外一個(gè)LOCK TABLES時(shí),或當(dāng)服務(wù)器的連接被關(guān)閉時(shí),當(dāng)前線程鎖定的所有表會(huì)自動(dòng)被解鎖。
如果一個(gè)線程獲得在一個(gè)表上的一個(gè)READ鎖,該線程和所有其他線程只能從表中讀。 如果一個(gè)線程獲得一個(gè)表上的一個(gè)WRITE鎖,那么只有持鎖的線程READ或WRITE表,其他線程被阻止。
已經(jīng)是有種隊(duì)列的味道,對(duì)不,所以解決方案很簡(jiǎn)單嘛,在select前加鎖,執(zhí)行完后面邏輯代碼后解鎖。或許有沒(méi)有人會(huì)有一個(gè)疑問(wèn),就是如果萬(wàn)一鎖表后線程就斷掉了那么是不是就一直鎖表了,這個(gè)確實(shí)是可能存在但是既然你想到了那么數(shù)據(jù)庫(kù)的設(shè)計(jì)人員也一定考慮到了,可以告訴你關(guān)于unlock的一些資料:當(dāng)線程發(fā)出另一個(gè) LOCK TABLES,或當(dāng)與服務(wù)器的連接被關(guān)閉時(shí),被當(dāng)前線程鎖定的所有表將被自動(dòng)地解鎖。這下放心了吧。
好,看下改進(jìn)后的代碼。
代碼如下 | 復(fù)制代碼 |
$db->lock( 'goods', 2 ); $sql = "select number from goods where id=1"; $number = intval( $db->result( $db->query( $sql ), 0 ) ); if ( $number > 0 ) { sleep( 2 ); $sql = "update goods set number=number-1 where id = 1"; if ( $db->query( $sql ) ) { echo 'Ok!Here you are!'; } else { echo 'Sorry!Something go wrong!Try it again.'; } } else { echo 'No more!you are so late!'; } $db->unlock(); |
只加了兩行代碼,不過(guò)也不能這么說(shuō),因?yàn)閜aperen我修改了自己那個(gè)操作數(shù)據(jù)庫(kù)的類,加了兩個(gè)方法lock與unlock,其實(shí)這兩個(gè)方法也很簡(jiǎn)單。
代碼如下 | 復(fù)制代碼 |
/** * 鎖表 * @param string $table 表名 * @param int $type 讀鎖1還是寫(xiě)鎖2 */ public function lock( $table, $type = 1 ) { $type = ( $type == 1 ) ? 'READ' : 'WRITE'; $this->query( "LOCK TABLE `$table` $type" ); } /** * 解鎖 */ public function unlock() { $this->query( "UNLOCK TABLES" ); } |
新聞熱點(diǎn)
疑難解答
圖片精選
網(wǎng)友關(guān)注