一、引言

  随着业务量的增加,单机部署已经无法满足日常需求了,我们可能会把代码部署到多台服务器上去来进行服务的扩容,也就是负载均衡,那在这种场景下,怎么能实现锁的概念呢?

  那么我们知道如果是一台主机部署的话,我们有很多方式可以实现锁的概念,比如利用synchronized关键字实现同步,或者使用reentrantLock可重入锁来在需要同步的场景,因为内存都是在一台机器上,可以很容易的实现对共享资源的锁定,但是多主机部署时,如果我们要锁定一个共享资源,显然这种方式无法满足我们的需求了,因为多主机部署时,我们每个主机要读取共享资源,可能会存在先后的顺序,那么在同时操纵一个数据时,就可能会存在一些很严重的问题了。

二、举个栗子

场景:2个用户同时对一个商品进行下单,商品库存只剩下1个,服务端会进行库存扣减。

期望结果:1个用户能下单成功,另一个用户提示库存不足

可能存在的问题:主机1和主机2同时去读取一个商品的库存,都发现是1,然后都进行了库存扣减,扣减完成后主机1进行了提交,库存被修改为0,这个时候主机2再进行提交,库存再次减1变为-1,那不就凉凉了么?

解决方案:数据库锁,分布式锁等(这篇文章只讨论数据库层面做的锁,分布式锁可以戳这里~)

三、代码实现

  场景:商品库存为0 ,而此时进来了一个用户的购买线程

Oracle学习(十七)数据库锁在分布式系统里的应用(老板,乐观锁了解一下?~)_数据

方式一 :update语句的原子性(行锁特性)实现乐观锁

乐观锁:乐观锁假设认为数据一般情况下不会造成冲突,所以在数据进行提交更新的时候,才会正式对数据的冲突与否进行检测

-- 3、对商品id为1的商品进行库存扣减
UPDATE T_GDS_INFO
SET GDS_STOCK = GDS_STOCK - 1
WHERE
    GDS_ID = 1
AND GDS_ID IN (
-- 2、这里要再封装一个结果集给外层的update语句进行条件过滤,否则一条语句直接操作同一张表,即进行select又进行update会报错
    SELECT
        A.GDS_ID
    FROM
        (
        -- 1、查询库存大于等于1的商品(带入需要扣减库存的商品id)
            SELECT
                GDS_ID
            FROM
                T_GDS_INFO
            WHERE
                GDS_ID = 1
            AND GDS_STOCK >= 1
        ) A
);
  • 1.
  • 2.
  • 3.
  • 4.
  • 5.
  • 6.
  • 7.
  • 8.
  • 9.
  • 10.
  • 11.
  • 12.
  • 13.
  • 14.
  • 15.
  • 16.
  • 17.
  • 18.
  • 19.
  • 20.
  • 21.

    上面的sql可能看着这比较绕,我们来一波简易的版本:

-- 1、当库存大于等于1时才进行库存扣减
UPDATE T_GDS_INFO
SET GDS_STOCK = GDS_STOCK - 1
WHERE
    GDS_ID = 1
AND GDS_STOCK >= 1
  • 1.
  • 2.
  • 3.
  • 4.
  • 5.
  • 6.

方式二 :利用版本号实现乐观锁

  思路:表里增加一个版本号的字段进行控制,每次成功更新版本号增加1,当需要进行更新操作时,先查询出版本号,然后再把查询出来的版本号作为update语句的条件带入,此时如果多个线程请求同一条数据时,查询到的版本号虽然一致,但是只有第一个提交的线程能更新成功。

Oracle学习(十七)数据库锁在分布式系统里的应用(老板,乐观锁了解一下?~)_乐观锁_02

-- 1、查询商品信息 得到版本号
SELECT * FROM T_GDS_INFO;
-- 2、在语句里带入查询到的版本号,每次成功更新版本号+1
UPDATE T_GDS_INFO
SET GDS_STOCK = GDS_STOCK - 1,
 VERSION = VERSION + 1
WHERE
    GDS_ID = 1
AND VERSION = 1
-- 3、如果同时2个订购都读取到版本号是1,然后进行库存扣减,那么第一条UPDATE语句会成功,把版本号增加变成2,而第二条UPDATE语句带入条件的版本号还是1,则会更新失败
  • 1.
  • 2.
  • 3.
  • 4.
  • 5.
  • 6.
  • 7.
  • 8.
  • 9.
  • 10.

  PS:类似的还有用时间戳字段来进行版本控制的,思路类似,这里就不进行详细说明了