MySQL悲觀鎖Laravel的實現

實現一個小需求,有個test表,表里有個name字段,往表里插入數據,name不能重復,不使用laravel的unique

版本一代碼如下

// routes.php
Route::get('/pessimism', function () {
        $name = 'laravel';
        $count = DB::table('test')->where('name', $name)->count();
        if ($count <= 0) {
            DB::table('test')->insert(['name' => $name]);
            echo 1;
        } else {
            echo 0;
        }
});

看起來沒毛病,跑起來很順暢,這里我的host是127.0.0.1:85,自行修改,在終端執行

curl http://127.0.0.1:85/pessimism
// 結果打印1
curl http://127.0.0.1:85/pessimism
// 再次執行打印0

乍看沒問題,但是當并發達到一定數量的時候呢,這里使用Apache的ab test來模擬并發,Ubuntu14.04用戶使用命令 sudo apt-get install apache2-utils 安裝即可,然后在終端執行

ab -n 150 -c 100 http://127.0.0.1:85/pessimism

命令解釋:100為并發數,150為請求數,可以理解為有100個人同時去窗口辦理了業務,這樣的操作執行了150次(可能比喻不是很恰當)

返回結果如下

This is ApacheBench, Version 2.3 <$Revision: 1528965 $>
Copyright 1996 Adam Twiss, Zeus Technology Ltd, http://www.zeustech.net/
Licensed to The Apache Software Foundation, http://www.apache.org/

Benchmarking 127.0.0.1 (be patient)
Completed 100 requests
Finished 160 requests


Server Software:        nginx
Server Hostname:        127.0.0.1
Server Port:            85

Document Path:          /begin
Document Length:        1 bytes

Concurrency Level:      150
Time taken for tests:   21.110 seconds
Complete requests:      160
// 如果返回結果不一樣則結果會有大量的Failed requests
// 讓成功跟失敗返回的HTML的length一致即可,所以示例代碼返回1或0
Failed requests:        0
Total transferred:      99113 bytes
HTML transferred:       160 bytes
Requests per second:    7.58 [#/sec] (mean)
Time per request:       19791.002 [ms] (mean)
Time per request:       131.940 [ms] (mean, across all concurrent requests)
Transfer rate:          4.58 [Kbytes/sec] received

Connection Times (ms)
              min  mean[+/-sd] median   max
Connect:        0   20   6.4     21      26
Processing:   896 10984 5814.6  11055   21078
Waiting:      895 10984 5814.7  11055   21078
Total:        916 11004 5809.7  11076   21093

Percentage of the requests served within a certain time (ms)
  50%  11076
  66%  13995
  75%  16316
  80%  16987
  90%  18434
  95%  19976
  98%  20478
  99%  21091
 100%  21093 (longest request)

執行成功,發現插入了不止一條name為laravel的結果(結果以自己的為準,可能只有1條,或者不止兩條,可以修改name值多試幾次)

Paste_Image.png

為什么會出現這樣的情況?看下圖

時間 小明 小紅
T1 讀取name為laravel的count為1
T2 讀取name為laravel的count為1
T3 insert一條數據
T4 insert一條數據(事實上這時候count已經為0)

結果就是不應該插入的數據插入了

解決辦法是,使用悲觀鎖,在小明讀取數據的時候加鎖,小紅這時候可以讀,但是不能操作修改新增刪除,直到小明commit提交事務開鎖

網上對悲觀鎖的解釋是,在讀取數據時鎖住那幾行,其他對這幾行的更新需要等到悲觀鎖結束時才能繼續,這里要結合事務使用,打開兩個終端窗口

窗口一

Paste_Image.png

注意在select語句結尾那里加上了 for update

窗口二

Paste_Image.png

窗口一執行commit提交事務

Paste_Image.png

窗口二

Paste_Image.png

窗口一解鎖了之后窗口二就能繼續操作了,窗口一的select必須包裹在begin跟commit之間才能生效

修改版本一代碼如下

實現一

// 使用transaction閉包實現
Route::get('/pessimism', function () {
    DB::transaction(function () {
        $name = 'php';
        // 注意加上lockForUpdate,否則無效
        $count = DB::table('test')->where('name', $name)->lockForUpdate()->count();
        if ($count <= 0) {
            DB::table('test')->insert(['name' => $name]);
            echo 1;
        } else {
            echo 0;
        }
    });
});

實現二

Route::get('/pessimism', function () {
    DB::beginTransaction();
    $name = 'java';
    $count = DB::table('test')->where('name', $name)->lockForUpdate()->count();
    if ($count <= 0) {
        DB::table('test')->insert(['name' => $name]);
        echo 1;
    } else {
        echo 0;
    }
    DB::commit();
});

------------------------------更新與2019--07-30------------------------------
上面這種寫法出現錯誤不會自動rollback,感謝網友 追夢小窩 的補充,修改的版本如下

Route::get('/pessimism', function () {
    DB::beginTransaction();
    try {
        $name = 'java';
        $count = DB::table('test')->where('name', $name)->lockForUpdate()->count();
        if ($count <= 0) {
            DB::table('test')->insert(['name' => $name]);
            echo 1;
        } else {
            echo 0;
        }
        // 提交事務
        DB::commit();
    } catch (\Exception $e) {
        // 回滾事務
        DB::rollBack();
    }
});

最后用ab再測試一下

ab -n 150 -c 100 http://127.0.0.1:85/pessimism

測試多次均未出現重復數據

試想一下這樣的應用場景,抽獎活動只剩下一個獎品n個人同時抽中了這個獎品,如果不做處理很容易出現被多個人領取的情況,當然現實的情況更加復雜,還要結合實際場景做相應的變動修改?。?/p>

最后編輯于
?著作權歸作者所有,轉載或內容合作請聯系作者
平臺聲明:文章內容(如有圖片或視頻亦包括在內)由作者上傳并發布,文章內容僅代表作者本人觀點,簡書系信息發布平臺,僅提供信息存儲服務。
  • 序言:七十年代末,一起剝皮案震驚了整個濱河市,隨后出現的幾起案子,更是在濱河造成了極大的恐慌,老刑警劉巖,帶你破解...
    沈念sama閱讀 228,461評論 6 532
  • 序言:濱河連續發生了三起死亡事件,死亡現場離奇詭異,居然都是意外死亡,警方通過查閱死者的電腦和手機,發現死者居然都...
    沈念sama閱讀 98,538評論 3 417
  • 文/潘曉璐 我一進店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人,你說我怎么就攤上這事。” “怎么了?”我有些...
    開封第一講書人閱讀 176,423評論 0 375
  • 文/不壞的土叔 我叫張陵,是天一觀的道長。 經常有香客問我,道長,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 62,991評論 1 312
  • 正文 為了忘掉前任,我火速辦了婚禮,結果婚禮上,老公的妹妹穿的比我還像新娘。我一直安慰自己,他們只是感情好,可當我...
    茶點故事閱讀 71,761評論 6 410
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著,像睡著了一般。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發上,一...
    開封第一講書人閱讀 55,207評論 1 324
  • 那天,我揣著相機與錄音,去河邊找鬼。 笑死,一個胖子當著我的面吹牛,可吹牛的內容都是我干的。 我是一名探鬼主播,決...
    沈念sama閱讀 43,268評論 3 441
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了?” 一聲冷哼從身側響起,我...
    開封第一講書人閱讀 42,419評論 0 288
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后,有當地人在樹林里發現了一具尸體,經...
    沈念sama閱讀 48,959評論 1 335
  • 正文 獨居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內容為張勛視角 年9月15日...
    茶點故事閱讀 40,782評論 3 354
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發現自己被綠了。 大學時的朋友給我發了我未婚夫和他白月光在一起吃飯的照片。...
    茶點故事閱讀 42,983評論 1 369
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖,靈堂內的尸體忽然破棺而出,到底是詐尸還是另有隱情,我是刑警寧澤,帶...
    沈念sama閱讀 38,528評論 5 359
  • 正文 年R本政府宣布,位于F島的核電站,受9級特大地震影響,放射性物質發生泄漏。R本人自食惡果不足惜,卻給世界環境...
    茶點故事閱讀 44,222評論 3 347
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧,春花似錦、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 34,653評論 0 26
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至,卻和暖如春,著一層夾襖步出監牢的瞬間,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 35,901評論 1 286
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留,地道東北人。 一個月前我還...
    沈念sama閱讀 51,678評論 3 392
  • 正文 我出身青樓,卻偏偏與公主長得像,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當晚...
    茶點故事閱讀 47,978評論 2 374

推薦閱讀更多精彩內容

  • 從三月份找實習到現在,面了一些公司,掛了不少,但最終還是拿到小米、百度、阿里、京東、新浪、CVTE、樂視家的研發崗...
    時芥藍閱讀 42,320評論 11 349
  • MySQL技術內幕:InnoDB存儲引擎(第2版) 姜承堯 第1章 MySQL體系結構和存儲引擎 >> 在上述例子...
    沉默劍士閱讀 7,448評論 0 16
  • Spring Cloud為開發人員提供了快速構建分布式系統中一些常見模式的工具(例如配置管理,服務發現,斷路器,智...
    卡卡羅2017閱讀 134,785評論 18 139
  • 什么是事務 事務是一條或多條數據庫操作語句的組合,具備ACID,4個特點。 原子性:要不全部成功,要不全部撤銷 隔...
    jiangmo閱讀 1,092評論 0 3
  • 小學三年級的時候,放學回家碰到一批一批的軍車駛過馬路,我和衛星一直等著軍車走完才回家,那時候我們都戴上了紅領巾,成...
    廢狗李子閱讀 339評論 0 0