悲觀鎖介紹(百科):
悲觀鎖,正如其名,它指的是對(duì)數(shù)據(jù)被外界(包括本系統(tǒng)當(dāng)前的其他事務(wù),以及來(lái)自外部系統(tǒng)的事務(wù)處理)修改持保守態(tài)度,因此,在整個(gè)數(shù)據(jù)處理過(guò)程中,將數(shù)據(jù)處于鎖定狀態(tài)。悲觀鎖的實(shí)現(xiàn),往往依靠數(shù)據(jù)庫(kù)提供的鎖機(jī)制(也只有數(shù)據(jù)庫(kù)層提供的鎖機(jī)制才能真正保證數(shù)據(jù)訪問(wèn)的排他性,否則,即使在本系統(tǒng)中實(shí)現(xiàn)了加鎖機(jī)制,也無(wú)法保證外部系統(tǒng)不會(huì)修改數(shù)據(jù))。
使用場(chǎng)景舉例:以MySQL InnoDB為例
商品goods表,假設(shè)商品的id為1,購(gòu)買數(shù)量為1,status為1表示上架中,2表示下架。現(xiàn)在用戶購(gòu)買此商品,在不是高并發(fā)的情況下處理邏輯是:
- 查找此商品的信息;
- 檢查商品庫(kù)存是否大于購(gòu)買數(shù)量;
- 修改商品庫(kù)存和銷量;
上面這種場(chǎng)景在高并發(fā)訪問(wèn)的情況下很可能會(huì)出現(xiàn)問(wèn)題。如果商品庫(kù)存是100個(gè),高并發(fā)的情況下可能會(huì)有1000個(gè)同時(shí)訪問(wèn),在到達(dá)第2步的時(shí)候,都會(huì)檢測(cè)通過(guò)。這樣會(huì)出現(xiàn)商品庫(kù)存是-900個(gè)的情況。顯然著不滿足需求!!!
商品表結(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;
使用悲觀鎖處理。
當(dāng)我們?cè)诓樵兂鰃oods信息后就把當(dāng)前的數(shù)據(jù)鎖定,直到我們修改完畢后再解鎖。那么在這個(gè)過(guò)程中,因?yàn)間oods被鎖定了,就不會(huì)出現(xiàn)有第三者來(lái)對(duì)其進(jìn)行修改了。
注:要使用悲觀鎖,我們必須關(guān)閉mysql數(shù)據(jù)庫(kù)的自動(dòng)提交屬性,因?yàn)镸ySQL默認(rèn)使用autocommit模式,也就是說(shuō),當(dāng)你執(zhí)行一個(gè)更新操作后,MySQL會(huì)立刻將結(jié)果進(jìn)行提交。thinkphp6中使用事務(wù),手動(dòng)進(jìn)行提交回滾。
<?php namespace appcontroller; use appBaseController; use thinkfacadeDb; 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 "庫(kù)存不足"; } $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) { // 回滾事務(wù) Db::rollback(); echo $e->getMessage(); exit("rollback"); } echo "請(qǐng)求成功"; } /** * 加鎖--悲觀鎖 * @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 "庫(kù)存不足"; } $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) { // 回滾事務(wù) Db::rollback(); echo $e->getMessage(); exit("rollback"); } echo "請(qǐng)求成功"; } }
使用jmeter工具測(cè)試,創(chuàng)建線程測(cè)試組:
關(guān)于使用jmeter創(chuàng)建測(cè)試高并發(fā)例子,可查看:使用JMeter進(jìn)行高并發(fā)測(cè)試_左右..的博客-CSDN博客_jmeter高并發(fā)測(cè)試
這里模擬1s內(nèi)1000個(gè)用戶同時(shí)訪問(wèn)
創(chuàng)建http請(qǐng)求:
添加察看結(jié)果樹(shù):
測(cè)試開(kāi)始:
100個(gè)商品不加鎖的結(jié)果:
100個(gè)商品庫(kù)存,生成訂單187個(gè),超賣87個(gè)商品,這在項(xiàng)目開(kāi)發(fā)中是絕對(duì)不允許的。
100個(gè)商品,加鎖結(jié)果:
加鎖,得到解決。
到此這篇關(guān)于thinkphp6使用mysql悲觀鎖解決商品超賣問(wèn)題的實(shí)現(xiàn)的文章就介紹到這了,更多相關(guān)thinkphp6 商品超賣內(nèi)容請(qǐng)搜索服務(wù)器之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持服務(wù)器之家!
原文鏈接:https://blog.csdn.net/W07028057/article/details/121455332