MySQL(表鎖)、PHP(檔案鎖)鎖機制及應用場景
阿新 • • 發佈:2018-12-22
模擬高併發訪問一個指令碼:apache安裝檔案的bin/ab.exe可以模擬併發量
C:\phpStudy\Apache\bin>ab.exe -c 10 -n 10 http://localhost/try.php // -c 模擬多少併發量 -n 一共請求多少次 http://請求的指令碼
Mysql中的鎖語法:
LOCK TABLE 表名1 READ|WRITE, 表名2 READ|WRITE .................. 【鎖表】
UNLOCK TABLES 【釋放表】
Read:讀鎖|共享鎖 : 所有的客戶端只能讀這個表不能寫這個表
Write:寫鎖|排它鎖: 所有當前鎖定客戶端可以操作這個表,其他客戶端只能阻塞
注意:在鎖表的過程中只能操作被鎖定的表,如果要操作其他表,必須把所有要操作的表都鎖定起來!
PHP中的檔案鎖 :
檔案鎖的檔案與表有什麼關係?:一點關係也沒有,與令牌相似,誰拿到誰操作。所以表根本沒鎖。
測試時,有個檔案就行,叫什麼名無所謂
總結:
專案中應該只使用PHP中的檔案鎖,儘量避免鎖表,因為如果表被鎖定了,那麼整個網站中所有和這個表相關的功能都被拖慢了(例如:前臺很多使用者一直下訂單,商品表mysql鎖表,其他與商品表相關的操作一直處於阻塞狀態【讀不出來商品表】,因為一個功能把整個網站速度拖慢)。
比如在一個O2O外賣專案中,中午12-2點,晚上6點都是訂單高併發時,這種情況下,MySQL鎖顯然是不考慮的,使用者體驗太差。其實根據實際的需求,外賣可以不用設計庫存量的,當然除了秒殺活動模組還是需要php檔案鎖的。
應用場景:
1. 高併發下單時,減庫存量時要加鎖
2. 高併發搶單、搶票時要使用
Mysql鎖示例:
/* 模擬秒殺活動-- 商品100件 CREATE TABLE ta ( id int comment '模擬100件活動商品的數量' ); INSERT INTO ta VALUES(100); 模仿:以10的併發量訪問這個指令碼! 使用apache自帶的ab.exe軟體 */ // 關閉錯誤報告 error_reporting(0); $dbhost = 'localhost:3306'; //mysql伺服器主機地址 $dbuser = 'root'; // mysql使用者名稱 $dbpass = 'root'; // mysql使用者名稱密碼 $conn = mysqli_connect($dbhost, $dbuser, $dbpass); if(! $conn ) { die('連線失敗: ' . mysqli_error($conn)); } // 設定編碼,防止中文亂碼 mysqli_query($conn , "set names utf8"); mysqli_select_db( $conn, 'temp' ); # mysql 鎖 mysqli_query($conn , 'LOCK TABLE a WRITE');// 只有一個客戶端可以鎖定表,其他客戶端阻塞在這 $rs = mysqli_query($conn , 'SELECT id FROM a'); $id = mysqli_result($rs, 0, 0); if($id > 0) { --$id; mysqli_query($conn , 'UPDATE a SET id='.$id); } # mysql 解鎖 mysqli_query($conn , 'UNLOCK TABLES'); //查詢解鎖後的id值 // $res = mysqli_query($conn , 'SELECT id FROM ta'); // while($row = mysqli_fetch_assoc($res)) // { // $id = $row['id']; // } // echo $id;
PHP檔案鎖示例:
/* 模擬秒殺活動-- 商品100件 CREATE TABLE ta ( id int comment '模擬100件活動商品的數量' ); INSERT INTO ta VALUES(100); 模仿:以10的併發量訪問這個指令碼! 使用apache自帶的ab.exe軟體 */ // 關閉錯誤報告 error_reporting(0); $dbhost = 'localhost:3306'; // mysql伺服器主機地址 $dbuser = 'root'; // mysql使用者名稱 $dbpass = 'root'; // mysql使用者名稱密碼 $conn = mysqli_connect($dbhost, $dbuser, $dbpass); if(! $conn ) { die('連線失敗: ' . mysqli_error($conn)); } // 設定編碼,防止中文亂碼 mysqli_query($conn , "set names utf8"); mysqli_select_db( $conn, 'temp' ); # php中的檔案鎖 $fp = fopen('./a.lock', 'r'); // php的檔案鎖和表沒關係,隨便一個檔案即可 flock($fp, LOCK_EX);// 排他鎖 $retval = mysqli_query($conn ,'SELECT id FROM ta'); while($row = mysqli_fetch_assoc($retval)) { $id = $row['id']; } if($id > 0) { --$id; mysqli_query($conn ,'UPDATE ta SET id='.$id); } # php的檔案鎖,釋放鎖 flock($fp, LOCK_UN); fclose($fp); // $res = mysqli_query($conn , 'SELECT id FROM ta'); // while($row = mysqli_fetch_assoc($res)) // { // $id = $row['id']; // } // echo $id;
搶券活動例項:
public function envelopeSnatching(){ $lingqu = $_POST['type']; $uid=session('u_id');//使用者id if(!$uid){ $data['msg']='您沒登入,請先登入!'; }else if(date('Y-m-d') != '2017-12-12'){ $data['msg']='不在活動時間內!'; }else{ $hours=date('H');//當前小時數 if($hours > '09' || $hours > '17'){ if($lingqu == 1 || $lingqu ==2){//點選10點的 if($lingqu == 1){ $is_lingqu=M('active_log')->field('id')->where(array('num_id'=>1,'uid'=>$uid))->find();//判斷是否領取過 if($hours > '09'){ $num=mt_rand(25,28);//優惠券金額 $id=1; } }else if($lingqu == 2){ $is_lingqu=M('active_log')->field('id')->where(array('num_id'=>2,'uid'=>$uid))->find(); if($hours > '17'){ $num=mt_rand(50,55);//優惠券金額 $id=2; } } if(!$id){ $data['msg']='時間還沒到,晚點再來吧。'; }else{ if($is_lingqu){ $data['msg']='你已經領取過了,留點給別人吧!'; }else{ //鎖表 M()->execute('LOCK TABLE active_num WRITE,active_log WRITE'); $active=M('active_num')->where(array('id'=>$id))->find(); if($active > 0){ //開啟事務 M()->execute('start transaction'); $save=M('active_num')->save(array('id'=>$id,'num'=>$active['num']-1)); $add=M('active_log')->add(array('uid'=>$uid,'money'=>$num,'num_id'=>$id,'addtime'=>time())); $members_preferential = M('members_preferential'); //對應投資金額, $key=array_search($num,array_reverse(C('PREFERENTIAL_JL'),true)); $PREFERENTIAL_ENDDAY=C('PREFERENTIAL_ENDDAY'); $endtime=strtotime("+{$PREFERENTIAL_ENDDAY}day");//過期時間 $add2=$members_preferential->add(array('uid'=>$uid,'type'=>$key,'endtime'=>$endtime));//新增優惠券 if($save && $add && $add2){ //事務提交 M()->execute('commit'); $data['msg']='恭喜你領取了'.$num.'元優惠券'; }else{ //回滾 M()->execute('rollback'); $data['msg']='未知錯誤!'; } }else{ $data['msg']='紅包已領完,你來晚了!'; } M()->execute('UNLOCK TABLES'); } } }else{ $data['msg']='非法操作!'; } }else{ $data['msg']='還沒有到活動時間,請晚點再來喲!!'; } } exit(json_encode($data)); }