thinkphp6使用mysql悲觀鎖解決商品超賣問題的實現(xiàn)
悲觀鎖介紹(百科):
悲觀鎖,正如其名,它指的是對數(shù)據(jù)被外界(包括本系統(tǒng)當前的其他事務,以及來自外部系統(tǒng)的事務處理)修改持保守態(tài)度,因此,在整個數(shù)據(jù)處理過程中,將數(shù)據(jù)處于鎖定狀態(tài)。悲觀鎖的實現(xiàn),往往依靠數(shù)據(jù)庫提供的鎖機制(也只有數(shù)據(jù)庫層提供的鎖機制才能真正保證數(shù)據(jù)訪問的排他性,否則,即使在本系統(tǒng)中實現(xiàn)了加鎖機制,也無法保證外部系統(tǒng)不會修改數(shù)據(jù))。
使用場景舉例:以MySQL InnoDB為例
商品goods表,假設(shè)商品的id為1,購買數(shù)量為1,status為1表示上架中,2表示下架?,F(xiàn)在用戶購買此商品,在不是高并發(fā)的情況下處理邏輯是:
- 查找此商品的信息;
- 檢查商品庫存是否大于購買數(shù)量;
- 修改商品庫存和銷量;
上面這種場景在高并發(fā)訪問的情況下很可能會出現(xiàn)問題。如果商品庫存是100個,高并發(fā)的情況下可能會有1000個同時訪問,在到達第2步的時候,都會檢測通過。這樣會出現(xiàn)商品庫存是-900個的情況。顯然著不滿足需求?。?!
商品表結(jié)構(gòu):
CREATE TABLE `goods` ( `id` int(11) unsigned NOT NULL AUTO_INCREMENT, `name` varchar(100) COLLATE utf8_unicode_ci NOT NULL DEFAULT '', `status` tinyint(1) NOT NULL DEFAULT '1', `total` int(11) NOT NULL DEFAULT '0', `sell` int(11) NOT NULL DEFAULT '100', `price` decimal(10,2) NOT NULL, PRIMARY KEY (`id`) ) ENGINE=InnoDB AUTO_INCREMENT=1 DEFAULT CHARSET=utf8 COLLATE=utf8_unicode_ci; INSERT INTO `test`.`goods`(`id`, `name`, `status`, `total`, `sell`, `price`) VALUES (1, '商品', 1, 0, 100, 15.00);
訂單表結(jié)構(gòu):
CREATE TABLE `orders` ( `id` int(11) unsigned NOT NULL AUTO_INCREMENT, `uid` int(11) NOT NULL DEFAULT '0', `create_time` datetime NOT NULL, `status` tinyint(1) NOT NULL DEFAULT '1', `goods_id` int(11) NOT NULL DEFAULT '0', `order_no` varchar(200) COLLATE utf8_unicode_ci NOT NULL DEFAULT '', PRIMARY KEY (`id`) ) ENGINE=InnoDB AUTO_INCREMENT=1 DEFAULT CHARSET=utf8 COLLATE=utf8_unicode_ci;
使用悲觀鎖處理。
當我們在查詢出goods信息后就把當前的數(shù)據(jù)鎖定,直到我們修改完畢后再解鎖。那么在這個過程中,因為goods被鎖定了,就不會出現(xiàn)有第三者來對其進行修改了。
注:要使用悲觀鎖,我們必須關(guān)閉mysql數(shù)據(jù)庫的自動提交屬性,因為MySQL默認使用autocommit模式,也就是說,當你執(zhí)行一個更新操作后,MySQL會立刻將結(jié)果進行提交。thinkphp6中使用事務,手動進行提交回滾。
<?php namespace app\controller; use app\BaseController; use think\facade\Db; class Test extends BaseController { /** * 不加鎖 * @return string|void */ public function test_1() { $num = 1; $goods_id = 1; Db::startTrans(); try { $where = []; $where['id'] = $goods_id; $where['status'] = 1; $goods_info = Db::table('goods')->where($where)->find(); if (empty($goods_info)) { return '商品不存在'; } $total = $goods_info['total']; $sell = $goods_info['sell']; if ($total < $num) { return '庫存不足'; } $data['total'] = $total-$num; $data['sell'] = $sell+$num; $res = Db::table('goods')->where(['id'=>$goods_id])->update($data); $order_data = []; $order_data['uid'] = rand(1000,9999); $order_data['status'] = 1; $order_data['create_time'] = date('Y-m-d H:i:s'); $order_data['goods_id'] = $goods_id; $order_data['order_no'] = date('YmdHis').rand(1000,10000); $order_res = Db::table('orders')->insert($order_data); Db::commit(); } catch (\Exception $e) { // 回滾事務 Db::rollback(); echo $e->getMessage(); exit('rollback'); } echo '請求成功'; } /** * 加鎖--悲觀鎖 * @return string|void */ public function test_2() { $num = 1; $goods_id = 1; Db::startTrans(); try { $where = []; $where['id'] = $goods_id; $where['status'] = 1; $goods_info = Db::table('goods')->lock(true)->where($where)->find(); if (empty($goods_info)) { return '商品不存在'; } $total = $goods_info['total']; $sell = $goods_info['sell']; if ($total < $num) { return '庫存不足'; } $data['total'] = $total-$num; $data['sell'] = $sell+$num; $res = Db::table('goods')->where(['id'=>$goods_id])->update($data); $order_data = []; $order_data['uid'] = rand(1000,9999); $order_data['status'] = 1; $order_data['goods_id'] = $goods_id; $order_data['order_no'] = date('YmdHis').rand(1000,10000); $order_data['create_time'] = date('Y-m-d H:i:s'); $order_res = Db::table('orders')->insert($order_data); Db::commit(); } catch (\Exception $e) { // 回滾事務 Db::rollback(); echo $e->getMessage(); exit('rollback'); } echo '請求成功'; } }
使用jmeter工具測試,創(chuàng)建線程測試組:
關(guān)于使用jmeter創(chuàng)建測試高并發(fā)例子,可查看:使用JMeter進行高并發(fā)測試_左右..的博客-CSDN博客_jmeter高并發(fā)測試
這里模擬1s內(nèi)1000個用戶同時訪問
?創(chuàng)建http請求:
添加察看結(jié)果樹:
?測試開始:
100個商品不加鎖的結(jié)果:
100個商品庫存,生成訂單187個,超賣87個商品,這在項目開發(fā)中是絕對不允許的。
100個商品,加鎖結(jié)果:
?加鎖,得到解決。
到此這篇關(guān)于thinkphp6使用mysql悲觀鎖解決商品超賣問題的實現(xiàn)的文章就介紹到這了,更多相關(guān)thinkphp6 商品超賣內(nèi)容請搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!
- thinkphp6中Redis 的基本使用方法詳解
- Thinkphp6.0中間件的具體使用
- ThinkPHP6.0前置、后置中間件區(qū)別
- ThinkPHP6通過Ucenter實現(xiàn)注冊登錄的示例代碼
- ThinkPHP6.0如何利用自定義驗證規(guī)則規(guī)范的實現(xiàn)登陸
- 基于thinkphp6.0的success、error實現(xiàn)方法
- 使用composer安裝使用thinkphp6.0框架問題【視頻教程】
- windows?server?2012?r2?服務器部署tp6?項目
- ThinkPHP5中如何使用redis
- 寶塔中ThinkPHP框架使用Redis的一系列教程
- ThinkPHP自定義Redis處理SESSION的實現(xiàn)方法
- Thinkphp6 配置并使用redis圖文詳解
相關(guān)文章
windows下apache搭建php開發(fā)環(huán)境
本文詳細介紹了在Windows2003下使用Apache2.2.21/PHP5.3.5/Mysql5.5.19/phpMyAdmin3.4.9搭建php開發(fā)環(huán)境,本文介紹的非常詳細,需要的朋友快來學習吧2015-08-08Ubuntu 16.04中Laravel5.4升級到5.6的步驟
這篇文章主要給大家介紹了關(guān)于在Ubuntu 16.04中Laravel5.4升級到5.6的相關(guān)資料,文中通過示例代碼介紹的非常詳細,對大家的學習或者工作具有一定的參考學習價值,需要的朋友們下面來一起學習學習吧2018-12-12linux實現(xiàn)php定時執(zhí)行cron任務詳解
linux實現(xiàn)php定時執(zhí)行cron任務2013-12-12Laravel5.1 框架登錄和注冊實現(xiàn)方法詳解
這篇文章主要介紹了Laravel5.1 框架登錄和注冊實現(xiàn)方法,結(jié)合實例形式詳細分析了laravel5.1框架登錄與注冊相關(guān)配置、路由、實現(xiàn)方法與操作注意事項,需要的朋友可以參考下2020-01-01