假设
有这么一个场景,库存表中一条产品库存数为50,如果我们需要增加20的库存数,则需要先查出当前库存数,在业务层上做新增库存数和当前库存数相加后再进行update的更新。
但是库存数在生产环境中他是浮动的,当我们查询完当前库存数,在业务层做库存相加运算逻辑时,及有可能其他业务在这时下单并进行了库存的扣减,从而导致最后更新库存数不准确。
为了解决以上类似的场景问题,因此提出了悲观锁和乐观锁的概念,目的都是为了数据在查询出来到修改,数据始终保持一致性。
悲观锁
悲观锁的实现需要依靠使用数据库的排他锁(for update),并且需要关闭mysql数据库的自动提交属性,因为mysql默认使用autocommit模式,也就是说,当你执行一个更新操作后,mysql会立刻将结果进行提交。
悲观锁执行流程:
//0.先把MYSQL自动提交改为0
set autocommit=0;
//1.开始事务
begin;
//2.先在库存中心查出id为1的库存数 #假设值为50
select Inventory from Inventory_center where id=1 for update;(加了排他锁的查询)
//3.对id为1的库存增加20库存数
update Inventory_center set inventory =70 where id=1;
//4.提交事务
commit;
上面的查询语句中,我们使用了select…for update的方式,这样就通过开启排他锁的方式实现了悲观锁。此时在Inventory_center表中,id为1的那条数据就被我们锁定了,其它的事务必须等本次事务提交之后才能执行。这样我们可以保证当前的数据不会被其它事务修改,直到我们更新完数据。
需要注意排他锁需要用主键id为判断,如果用别的条件,会使锁定整张表。锁表的消耗小,但锁表并发会受到限制。
Select * from 表名 where 主键id=1 for update;(锁行不锁表)
Select * from 表名 where 主键id=不存在的值 for update;(不会使用锁)
Select * from 表名 where name=’jiani’for update;(没有用主键作为判断条件会锁住整张表)
乐观锁
乐观锁的实现需要在数据表里加个version字段,当update更新时 version+1,乐观锁每次更新数据时都需要比对version字段是否与查询时一致,一致则更新成功,不一致则说明数据在这期间被其他线程修改,更新则失败。
乐观锁执行流程:
//0.查询id为1的库存数和当前version值。
select Inventory,version from Inventory_center where id=1
#假设查询结果为Inventory=50,version=5
//1.更新库存数,库存数+20 #并比对version值是否与查询时一致。
int 1= update Inventory_center set Inventory=70,version=version+1 where id=1 and version=5;
//2.更新失败则可采用递归的方式重新更新。
if (int =0){ 递归重新更新 }
结言
最后说下两种锁各自的使用场景,悲观锁适合用于并发写入多、临界区代码复杂、竞争激烈等场景,这种场景下悲观锁可以避免大量的无用的反复尝试等消耗。乐观锁适用于大部分是读取,少部分是修改的场景,也适合虽然读写都很多,但是并发并不激烈的场景。在这些场景下,乐观锁不加锁的特点能让性能大幅提高。