使用Mysql悲观锁解决并发问题

使用Mysql悲观锁解决并发问题

最近无聊学习了一下Mysql的悲观锁和乐观锁,根据网上的资料和自己的理解总结如下;

悲观锁介绍(百度百科)

悲观锁,正如其名,它是指对于数据被外界(包括本系统当前的事务以及外部系统的其他事务)的修改持保守态度。意思就是,在整个的数据处理的过程中,将所处理的数据处于锁定状态,这样其他的事务就无法在本事务处理过程当中对数据进行修改,也就不会出现脏读(查询到未提交的数据,即脏数据)的现象。悲观锁的实现,主要依赖于数据库的锁机制(也只有数据库提供的锁机制才能真正的实现数据访问的排他性,否则,即使在本系统中实现了加锁的机制,也无法保证外部的系统不会修改数据)。

场景举例

现在有三个表,一个是用户表(user_info),一个是商品表(goods_info),还有个订单表(order_info)。用户表里面有两用户,他们两个都想买一个充电宝,但是充电宝只有一个,当status为0时,代表商品存在,为1时则表示商品已被售出。
使用Mysql悲观锁解决并发问题

  • 首先,不采用锁的情况如下:
    ①查询商品信息,获取商品状态,判断是否为0;
    select * from goods_info where id=‘1’;
    ②然后根据商品的信息生成订单到订单表;
    insert into order_info (userId,goodsId) values(null,1);
    ③订单表插入成功后,修改商品表中商品的状态为1;
    update goods_info set status=1 where id=‘1’

上面这种场景在高并发访问的情况下很可能出现问题!!!
前面已经提到,商品从查出来到修改,中间有一个处理订单的过程,上面第一步的操作中查询出商品的状态为0,那就代表商品存在可以下单,但是当我们执行第三步update操作的时候,很可能有另外一个人,也下单了,并且先一步执行了update操作修改了商品的状态,这就造成了同一个商品被下单两次的情况,使数据不一致,所以这种方式是不安全的!!!加粗样式

  • 在使用了悲观锁以后:
    在上面的场景中,商品从查询状态到修改状态,中间有一个处理订单的过程,悲观锁的作用就是,在当我们执行第一步查询出goods表中的订单状态时,将数据进入锁定状态(我们这里指定了goods表的主键id,所以当前锁的等级就是行级锁,只会锁定当前行,不会锁定整个表),等我们执行完第三步把商品状态修改完成以后才会解锁,那么在锁定过程中,由于goods中id为1的数据就被锁定了,就不会有第三者来对其进行修改了。
    注:要使用悲观锁,我们必须关闭Mysql的自动提交属性,因为Mysql默认使用autocommit模式,当执行一个更新操作的时候,Mysql会立即将结果提交!!!!!!
  • 先要设置Mysql为非autocommit模式
    执行语句:set autocommit=0;
  • 设置完成后就可以进行我们正常的业务了
    开启事务
    begin;begin work;start transaction;(这三条命令选择一条即可)
    查询商品信息,获取商品状态,判断是否为0;
    select * from goods_info where id=‘1’ for update;
    然后根据商品的信息生成订单到订单表;
    insert into order_info (userId,goodsId) values(null,1);
    订单表插入成功后,修改商品表中商品的状态为1;
    update goods_info set status=1 where id=‘1’
    最后一步就是提交了;
    commit;commit work;(两条命令)
    注:上面的begin/commit是事务的开始和提交,因为我们在前一步关闭了Mysql的自动提交功能,所以这儿需要我们手动进行提交。
    这个地方用了select…for update的方式,这样就通过数据库实现了悲观锁,此时在goods订单表中,id为1的那条数据就被锁定了,其他事务必须等待这次事务处理完成后才能执行,这样就保证了当前数据不会被其他数据所修改。

此处有一点需要注意,只有SELECT … FOR UPDATE 或LOCK IN SHARE MODE 相同数据时会等待其它事务结束后才执行,如果直接用select。。。查询,不加for update,则不会受到悲观锁影响。拿上面的实例来说,我在执行完select status,id from goods_info where id=‘1’ for update;之后,在其他事务中再执行select status,id from goods_info where id=‘1’ for update;此时的事务会进入暂时的阻塞状态,会在第一次的事务执行完以后再执行其他事务。但是,如果在其他事务中执行select * from goods_info where id=‘1’ ;则不会受到悲观搜的影响,会正常查出数据。

最后看都看了,码字不易,留个赞再走吧!!!