MySQL樂觀鎖在分佈式場景下的實踐

我的網站:www.upheart.top/java

在電商購物的場景下,當咱們點擊購物時,後端服務就會對相應的商品進行減庫存操做。在單實例部署的狀況,咱們能夠簡單地使用JVM提供的鎖機制對減庫存操做進行加鎖,防止多個用戶同時點擊購買後致使的庫存不一致問題。數據庫

但在實踐中,爲了提升系統的可用性,咱們通常都會進行多實例部署。而不一樣實例有各自的JVM,被負載均衡到不一樣實例上的用戶請求不能經過JVM的鎖機制實現互斥。後端

所以,爲了保證在分佈式場景下的數據一致性,咱們通常有兩種實踐方式:1、使用MySQL樂觀鎖;2、使用分佈式鎖。bash

本文主要介紹MySQL樂觀鎖,關於分佈式鎖我在下一篇博客中介紹。app

樂觀鎖簡介負載均衡

樂觀鎖(Optimistic Locking)與悲觀鎖相對應,咱們在使用樂觀鎖時會假設數據在極大多數狀況下不會造成衝突,所以只有在數據提交的時候,纔會對數據是否產生衝突進行檢驗。若是產生數據衝突了,則返回錯誤信息,進行相應的處理。dom

那咱們如何來實現樂觀鎖呢?通常採用如下方式:使用版本號(version)機制來實現,這是樂觀鎖最經常使用的實現方式。分佈式

版本號ide

那什麼是版本號呢?版本號就是爲數據添加一個版本標誌,一般我會爲數據庫中的表添加一個int類型的"version"字段。當咱們將數據讀出時,咱們會將version字段一併讀出;當數據進行更新時,會對這條數據的version值加1。當咱們提交數據的時候,會判斷數據庫中的當前版本號和第一次取數據時的版本號是否一致,若是兩個版本號相等,則更新,不然就認爲數據過時,返回錯誤信息。測試

代碼實踐

咱們對某個商品減庫存時,具體操做分爲如下3個步驟:

查詢出商品的具體信息

    根據具體的減庫存數量,生成相應的更新對象

    修改商品的庫存數量
複製代碼

爲了使用MySQL的樂觀鎖,咱們須要爲商品表goods加一個版本號字段version,具體的表結構以下:

CREATE TABLE `goods` (

 `id` int(11) NOT NULL AUTO_INCREMENT,

 `name` varchar(64) NOT NULL DEFAULT '',

 `remaining_number` int(11) NOT NULL,

 `version` int(11) NOT NULL,

 PRIMARY KEY (`id`)

) ENGINE=InnoDB AUTO_INCREMENT=2 DEFAULT CHARSET=utf8;
複製代碼

Goods類的Java代碼:

/* 商品名字
    */
   private String name;
   /**
    * 庫存數量
    */
   private Integer remainingNumber;
   /**
    * 版本號
    */
   private Integer version;

   @Override
   public String toString() {
       return "Goods{" +
               "id=" + id +
               ", name='" + name + '\'' + ", remainingNumber=" + remainingNumber + ", version=" + version + '}'; } } 複製代碼

GoodsMapper.java:

public interface GoodsMapper {
   Integer updateGoodCAS(Goods good);
}
複製代碼

GoodsMapper.xml以下:

<update id="updateGoodCAS" parameterType="com.ztl.domain.Goods">

       <![CDATA[
         update goods
         set `name`=#{name},
         remaining_number=#{remainingNumber},
         version=version+1
         where id=#{id} and version=#{version}
       ]]>

   </update>
複製代碼

GoodsService.java 接口以下:

public interface GoodsService {

   @Transactional
   Boolean updateGoodCAS(Integer id, Integer decreaseNum);

}
複製代碼

GoodsServiceImpl.java類以下:

@Service

public class GoodsServiceImpl implements GoodsService {

   @Autowired
   private GoodsMapper goodsMapper;

   @Override
   public Boolean updateGoodCAS(Integer id, Integer decreaseNum) {
       Goods good = goodsMapper.selectGoodById(id);
       System.out.println(good);
       try {
           Thread.sleep(3000);    
       } catch (InterruptedException e) {
           e.printStackTrace();
       }

       good.setRemainingNumber(good.getRemainingNumber() - decreaseNum);
       int result = goodsMapper.updateGoodCAS(good);
       System.out.println(result == 1 ? "success" : "fail");
       return result == 1;
   }

}
複製代碼

GoodsServiceImplTest.java測試類

@RunWith(SpringRunner.class)

@SpringBootTest

public class GoodsServiceImplTest {

   @Autowired
   private GoodsService goodsService;

   @Test
   public void updateGoodCASTest() {

       final Integer id = 1;
       Thread thread = new Thread(new Runnable() {
           @Override
           public void run() {
               goodsService.updateGoodCAS(id, 1);  
           }
       });

       thread.start();
       goodsService.updateGoodCAS(id, 2);          
       System.out.println(goodsService.selectGoodById(id));

   }

}
複製代碼

輸出結果:

Goods{id=1, name='手機', remainingNumber=10, version=9}
Goods{id=1, name='手機', remainingNumber=10, version=9}
success
fail
Goods{id=1, name='手機', remainingNumber=8, version=10}
複製代碼

代碼說明:

在updateGoodCASTest()的測試方法中,用戶1和用戶2同時查出id=1的商品的同一個版本信息,而後分別對商品進行庫存減1和減2的操做。從輸出的結果能夠看出用戶2的減庫存操做成功了,商品庫存成功減去2;而用戶1提交減庫存操做時,數據版本號已經改變,因此數據變動失敗。這樣,咱們就能夠經過MySQL的樂觀鎖機制保證在分佈式場景下的數據一致性。

相關文章
相關標籤/搜索