MySQL悲觀鎖Laravel的實現(xiàn)

實現(xiàn)一個小需求,有個test表,表里有個name字段,往表里插入數(shù)據(jù),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,自行修改,在終端執(zhí)行

curl http://127.0.0.1:85/pessimism
// 結(jié)果打印1
curl http://127.0.0.1:85/pessimism
// 再次執(zhí)行打印0

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

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

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

返回結(jié)果如下

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
// 如果返回結(jié)果不一樣則結(jié)果會有大量的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)

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

Paste_Image.png

為什么會出現(xiàn)這樣的情況?看下圖

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

結(jié)果就是不應(yīng)該插入的數(shù)據(jù)插入了

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

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

窗口一

Paste_Image.png

注意在select語句結(jié)尾那里加上了 for update

窗口二

Paste_Image.png

窗口一執(zhí)行commit提交事務(wù)

Paste_Image.png

窗口二

Paste_Image.png

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

修改版本一代碼如下

實現(xiàn)一

// 使用transaction閉包實現(xiàn)
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;
        }
    });
});

實現(xiàn)二

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------------------------------
上面這種寫法出現(xiàn)錯誤不會自動rollback,感謝網(wǎng)友 追夢小窩 的補充,修改的版本如下

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;
        }
        // 提交事務(wù)
        DB::commit();
    } catch (\Exception $e) {
        // 回滾事務(wù)
        DB::rollBack();
    }
});

最后用ab再測試一下

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

測試多次均未出現(xiàn)重復數(shù)據(jù)

試想一下這樣的應(yīng)用場景,抽獎活動只剩下一個獎品n個人同時抽中了這個獎品,如果不做處理很容易出現(xiàn)被多個人領(lǐng)取的情況,當然現(xiàn)實的情況更加復雜,還要結(jié)合實際場景做相應(yīng)的變動修改??!

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點,簡書系信息發(fā)布平臺,僅提供信息存儲服務(wù)。

推薦閱讀更多精彩內(nèi)容

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