只用資料庫設計高效搶購業務
不使用快取(redis、memcache),如何設計高效搶購業務呢?
常見的搶購業務主要有:
商品搶購
券搶購
紅包搶購
今天咱們就談談如何對這些搶購業務做統一設計,只使用Mysql做高併發活動。
我們先看下面一張表,分別標出了這三類業務涉及到的核心資料量:
核心資料量
資料量 | 商品庫存 | 券庫存 | 紅包庫存 | 資料金額 |
---|---|---|---|---|
1.總量 | totalNum | totalNum | totalMoney | 總金額 |
2.銷售量 | sellNum | sellNum | sellMoney | 領取金額 |
3.鎖定量 | lockNum | lockNum | 0 | 鎖定金額 |
4.庫存量 | stockNum | stockNum | stockMoney | 庫存金額 |
5.退貨量 | refundNum | refundNum | 0 | 0 |
6,庫存池 | inventory | inventory | - | - |
我們再看下這些業務涉及到的核心節點的資料量變化
核心資料量變化(變化量為n)
節點名稱 | 總量 | 銷售量 | 鎖定量 | 庫存量 | 退貨量 |
---|---|---|---|---|---|
下單 | totalNum不變 | sellNum不變 | lockNum+n | stockNum不變 | refundNum不變 |
取消訂單 | totalNum不變 | sellNum不變 | lockNum-n | stockNum不變 | refundNum不變 |
支付 | totalNum不變 | sellNum+n | lockNum-n | stockNum-n | refundNum不變 |
核銷 | totalNum不變 | sellNum不變 | lockNum不變 | stockNum不變 | refundNum不變 |
退款 | totalNum不變 | sellNum-n(或不變) | lockNum不變 | stockNum+n(或不變) | refundNum+n |
節點設計
我們知道,每個操作節點最少可對應一個操作。而我們為了保證介面效率最高,我們需要儘量少的使用sql操作,如果每個介面只有一次資料操作那是最好的。
但是,每個介面的邏輯可能都會涉及到若干資料業務操作,而我們為了能讓這些搶購業務效能儘量好,那我們就要儘量把介面原子化設計。而在這些介面中,下單操作時重中之重,只要把這個下單介面設計好了,後續流程基本都沒啥大問題。
下單介面設計
那我們來分析一下下單介面的設計業務:
檢查庫存是否夠用
防止併發超賣
鎖庫存
等等...
那這些要求改如何同時滿足呢?那就要我們儘量合理使用核心資料量了!
那我們在設計庫存表的時候,儘量把總量、銷售量、鎖定量、庫存量放到一起,可以如下設計:
CREATE TABLE `goods_stock` ( `ID` bigint(20) NOT NULL AUTO_INCREMENT COMMENT '主鍵id', `SPU_ID` bigint(20) NOT NULL DEFAULT '0' COMMENT '商品SPU_ID', `SKU_ID` bigint(20) NOT NULL DEFAULT '0' COMMENT '商品SKU ID', `STOCK_LEFT_NUM` int(11) NOT NULL DEFAULT '0' COMMENT '剩餘庫存數量', `STOCK_SALE_NUM` int(11) NOT NULL DEFAULT '0' COMMENT '售賣庫存數量', `STOCK_LOCK_NUM` int(11) NOT NULL DEFAULT '0' COMMENT '被鎖定的庫存數量', `STOCK_TOTAL_NUM` int(11) NOT NULL DEFAULT '0' COMMENT 'SKU總庫存量,0表示不限制庫存', `STATUS` tinyint(4) NOT NULL DEFAULT '0' COMMENT '本條記錄狀態,0-有效,1-無效', `CREATE_TIME` datetime NOT NULL COMMENT '建立時間', `UPDATE_TIME` timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '更新時間', `VERSION` int(11) NOT NULL DEFAULT '0' COMMENT '樂觀鎖版本號', PRIMARY KEY (`ID`), KEY `IDX_SPU_ID` (`SPU_ID`), KEY `IDX_SKU_ID` (`SKU_ID`) ) ENGINE=InnoDB DEFAULT CHARSET=utf8 COMMENT='商品SKU庫存表';
其他欄位在例項中省略了。
我們看看如何使用這一張表滿足上述要求:
1,檢查庫存是否夠用:
STOCK_LEFT_NUM >= n
2,防止併發超賣:
利用資料庫行級鎖防併發
WHERE SKU_ID = ${sku_id}
3,鎖庫存:
STOCK_LOCK_NUM = STOCK_LOCK_NUM + n
那麼,具體的下單、取消訂單、支付等業務節點就可以如下實現:
1,下單
UPDATE `goods_stock` SET `STOCK_LOCK_NUM` = `STOCK_LOCK_NUM` + ${lockNum} WHERE `STOCK_LEFT_NUM` <![CDATA[ >= ]]> ${lockNum} AND `SKU_ID` = ${skuId} AND ${lockNum} <![CDATA[ > ]]> 0
當該操作執行成功,再處理其他邏輯;如果失敗,就直接返回下單失敗。
2,取消訂單
UPDATE `goods_stock` SET `STOCK_LOCK_NUM` = `STOCK_LOCK_NUM` - ${lockNum} WHERE `SKU_ID` = ${skuId} AND ${lockNum} <![CDATA[ > ]]> 0
3,支付訂單
UPDATE `goods_stock` SET `STOCK_LOCK_NUM` = `STOCK_LOCK_NUM` - ${lockNum}, `STOCK_SALE_NUM` = `STOCK_SALE_NUM` + ${lockNum}, `STOCK_LEFT_NUM` = `STOCK_LEFT_NUM` - ${lockNum} WHERE `STOCK_LOCK_NUM` <![CDATA[ >= ]]> ${lockNum} AND `SKU_ID` = ${skuId} AND ${lockNum} <![CDATA[ > ]]> 0
目前MySql+SD硬碟的話,MySql事務併發量達到2000是沒啥壓力的,所以一般併發2000左右的都可以直接使用MySql資料庫設計業務邏輯,無需使用Redis快取。
這裡只是給出了商品庫存核心邏輯,券和其類似,不過還多一點庫存池(存放券碼),其實這個可以放到後臺去處理了;紅包比這個更簡單,只有下單(領紅包)操作。
-End-