起因:項目中要做預約功能,首先每天的余票都是有上限的,自然不能出現超賣的情況
基於我們項目是單體分布式的springcloud部署,我想了下😁
第一種方法,直接mysql加行鎖,要update這條庫存數據時,在數據庫表層面加上行鎖,直接禁止其他線程讀寫,就確保了這條庫存數據是被單線程操作的,不會出現超賣
第二種方法,把庫存數據放Redis,需要update時對緩存數據加鎖,也能保證該條庫存數據被單線程操作
第三種方法,是最簡單的方法,代碼實現悲觀鎖,也是最不專業的方法,就是在最終修改庫存的方法添加同步代碼塊,沒錯就是他,就是sync,更新庫存的方法單獨封裝成一個同步方法。但是這里有一點謹記,在同步代碼塊所在的方法類上千萬別添加@Transactional注解,這個坑同樣會造成超賣,因為,事務注解的操作范圍大於同步代碼塊,你想有種情況:正在賣第十部手機鎖釋放但事務未提交,這時新線程進入查出老庫存,又會把第十部手機再重復賣一次,老板會打爆你的狗頭的,記住去掉@Transactional注解。(第三個方法完全可以采用)
第四種方案:代碼加持在數據庫層面實現樂觀鎖,聽起來很復雜,其實不然。其實就是在數據庫為每條庫存數據添加唯一版本號version字段,查出來version是1,我更新的時候就加上這個version的 where 條件去更新(切記庫存的加減和version新增在sql層面編寫),version相同我才更新庫存,不相同不更新(這里根據update的sql返回值是否為1判斷是否更新成功即可),再重新循環查一次最新version即可(第四種方案不僅適用於單機,若你們項目時集群部署同樣是適合的,而且高效,代碼也簡單)
選擇與思考:
第三種用戶體驗不會特別好,而且只適用於單機部署的項目
個人比較推薦第四種樂觀鎖方案,代碼也簡單
后期如果並發特別大,有的線程由於等太久導致請求超時,需要考慮做做接口限流。