[PHP]基於MySQL簡單的庫存量流程處理接口設計

因爲iwebshop原生的庫存流程不太理想,參見:
iwebshop庫存量的設計缺點.txt(http://blog.csdn.net/gevolution90/article/details/8709706)


試用了一下ecshop的購買到下訂單的流程,發現下了訂單,並且在後臺點了支付及配送後,庫存量都沒有變。不知是什麼原因了,也懶得去看源代碼了。


用“php|mysql 電子商務|商城 庫存量 問題”關鍵詞在百度,Google.hk,德問上進行搜索,沒有找到太有價值的資料,於是自己設計了一下庫存量流程的處理接口。
btw,這個頁面有參考價值:
+商城併發搶購庫存減至負數問題(http://www.dewen.org/q/10230/%E5%95%86%E5%9F%8E%E5%B9%B6%E5%8F%91%E6%8A%A2%E8%B4%AD%E5%BA%93%E5%AD%98%E5%87%8F%E8%87%B3%E8%B4%9F%E6%95%B0%E9%97%AE%E9%A2%98)


要解決的問題首先是避免iwebshop原生庫存流程中庫存量輕易就被做空,然後是避免超售現象(即庫存變爲負數)。除此之外,不再考慮其它複雜的情況,不考慮如高併發,大流量,分佈式這類高級東西。意在爲最基本的環境:單臺服務器(比如虛擬主機),mysql甚至沒有innodb引擎的情況下,提供具有一定事務性的庫存量功能。


因此,最基本的設計原則是,事務完整性問題能交給數據庫解決的全交給數據庫(哪怕是MyISAM引擎),因爲數據庫有實現好的鎖(管它是表鎖還是行鎖)。若MySQL支持InnoDB引擎,則直接使用InnoDB的事務功能(所以具體會有兩個實現版本,一個MyISAM版本和一個InnoDB版本)。


所有接口使用一個類進行包裝,每個接口就是類中的一個方法。


先分析庫存量被清0的問題,在用戶實際支付之前(不考慮貨到付款的情況)就扣掉庫存量都會有被惡意用戶低成本清0庫存的風險。所以參考一些電子商務網站的方法(比如萬達電影在線購票),在選中要購買的物品後,有5分鐘的支付有效時間,超過時間未成功支付,所佔用的物品(比如影院坐位)會被還原。
電院坐位鎖定釋放的響應時間要求比較高,但庫存量要求不至於這麼高,所以容易實現很多。但是要考慮的另一個問題是一張購物訂單常會包含多個商品。


使用兩個數據表處理這個我稱爲“異步支付”的問題(即從開始支付到支付成功是有一定的時間間隔的):


異步ID表(store_asyn_id)
aid int 自增主鍵
timeout int 過期時間
索引:
timeout


異步庫存量記錄表(store_asyn_num)
anid int 自增主鍵
aid int 外鍵 store_asyn_id.aid
store_id int 外鍵 store.store_id
num smallint 庫存變動數量
索引:
aid
store_id


這兩個表在沒有innodb提供事務功能時也用於模擬事務,大概用法是:


開始處理一張訂單內商品的數量時,申請一個 aid (或重用一個已申請過的 aid),並設置一個較短的過期時間(比如1分鐘內),每變動一個庫存量便同時把相關數據寫入 store_asyn_num 表,在所有庫存變動完成後,再把過期時間設爲0(表示成功)。
一但庫存變動過程中意外中斷,可以通過超時時間和 store_asyn_num 表的記錄數據對庫存量進行回滾(以模擬事務的回滾功能)。
同樣的用法也用於異步支付,只要把過期時間設長一點(比如5~10分鐘)即可。


這兩個表是關鍵設計了,後面的就都是按步就班的普通設計。


使用一個表保存庫存量相關的數據:


庫存表(store)
store_id int 庫存ID,自增主鍵
gid int 分組ID(比如可以用於記錄商品ID)
sell int 出售庫存(支付成功後扣除)
deliver int 發貨庫存(發貨成功後扣除)
status tinyint 狀態{0:正常, 1:鎖定}
索引:
gid


庫存ID由庫存表自動生成,並反過來記錄在貨品數據中,以達到與具體商品或貨品的分離,這樣庫存接口就可以用於任何一個對一個數量有併發事務要求的地方,比如團購。
庫存量分兩種,一種是支付成功後扣除的,表示出售用的庫存量,另一個是發貨成功後扣除的,表示倉庫中實際的庫存量(發貨庫存)。發貨庫存主要是後臺管理中需要用到,不需要異步支付的概念。
一個被鎖定的庫存量相當於沒有庫存。


在用戶下訂單時,先檢查一下訂單中的貨品庫存量是否足夠(單純的檢查,不修改庫存量):
array check_sell($store)
$store array [store_id] => 所需扣除的數量
@return array {0:bool 檢查是否通過, 1:array 檢查不通過的庫存ID和剩餘數量, 2:array 所有檢查的庫存ID和剩餘數量}
可以用 list($is_ok, $store_not, $store_all) = check_sell(); 進行賦值。返回這麼多數據主要是給調用者生成錯誤提示信息。


後臺發貨時需要檢查一下發貨庫存是否足夠:
array check_deliver($store)
參數與返回值都與 check_sell 一致。


在貨到付款類的訂單中,需要直接扣除庫存量:
array take_sell($store)
參數與返回值與 check_sell 一致。


異步支付型,要求在訂單數據中記錄對應的異步ID,先檢查訂單是否申請過異步ID,若未申請,則申請一個並記錄回訂單數據中:
int asyn_newid()
返回一個新的異步ID


然後開始異步支付:
array take_sell_asyn($aid, $store, $timeout)
$aid int 異步ID
$store array 參見 check_sell 同名參數。
$timeout int 過期時間戳
@return array 參見 check_sell 返回值。


當用戶支付完成後,把一個異步ID標記爲完成(所以異步ID需要記錄在訂單數據中):
bool asyn_done($aid)


取消一個貨到付款類訂單:
bool cancel_sell($store)
$store array 參見 check_sell 同名參數


取消一個異步支付ID(比如用戶點擊了支付,在未成功支付時再主動點擊了取消訂單):
bool cancel_asyn($aid)


後臺發貨:
array take_deliver($store)
參數與返回值參見 check_sell.


後臺需要調整貨品的庫存量,不提供直接設置庫存量值,只提供增加和減少兩個功能:
增加出售庫存:
bool plus_sell($store_id, $num)
增加發貨庫存:
bool plus_deliver($store_id, $num)
減少出售庫存:
bool reduce_sell($store_id, $num)
減少發貨庫存:
bool reduce_deliver($store_id, $num)


提供一個凍結(鎖定)庫存的功能輔助在減少庫存時使用,被凍結的庫存等於沒有庫存(即不能被購買)。
凍結:
void lock($store_id)
解鎖:
void unlick($store_id)


對異步表中的過期數據進行清理,因爲 MyISAM 表不適合頻繁的寫操作(update/delete),因此建議定時執行清理(比如一小時一次):
asyn_clean()
被清理的庫存量會回滾回庫存表中。


asyn_repair($store_id)
針對一個庫存ID進行清理,以快速回滾一個庫存被佔用但已過期的庫存量。


asyn_get($aid)
取一個異步ID的過期時間。


asyn_activate($aid, $timeout)
重新激活一個過期的異步ID,只能激活已過期的ID,並且不能激活已完成和正在清理中的異步ID。


如果使用 MyISAM 引擎,可以把 update 語句寫成一句提交的巧讓 MySQL 處理併發鎖的問題:
UPDATE t1, t2 SET t1.num=t1.num+t2.num, t2.num=0 WHERE t1.id=t2.id AND ...
如果需要先INSERT再UPDATE,可以INSERT一個數量爲0的記錄,再進行UPDATE操作。
若是可以使用InnoDB引擎,使用事務進行一組操作就可以了。
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章