简单聊聊乐观锁 悲观锁

什么是悲观锁?

简单来讲,在数据库层概念简单来讲,就是比较悲观的认为事务每次过来都要修改同一个资源,那修改同一个资源在并发下肯定会出问题,所以我们要上锁,那同一时刻只允许一个数据库的事务来修改资源,体现在SQL上的那就是Select for update,先把这条记录锁住,其他的事物过来先等着,只有当当前的事务提交了,释放了锁,其他人才能够获取到锁。这样其实就相当于一个串行的操作,这是悲观锁。

什么是乐观锁?

就是比较乐观的认为每一个事务都是不更新数据,或者少量的更新数据,简单讲就是查的比较多,改的比较少。那我就对资源可以先不用加锁,大家正常的访问。那可能觉得不加锁并发读没问题,那并发写怎么办?这个其实就是乐观锁的核心。虽然没有锁,但是有了一个概念叫版本号。一个记录对应一个版本号,读的时候是一个版本号。写的时候,再把对应的版本号作为条件带进去,在where条件里面,那么更新完了之后,我们把版本号加1。那么如果写的时候,版本号跟之前的不一致,那就说明数据被其他的事务修改过了,那肯定不能更新了,要不然就出问题了。所以就要抛出更新失败的异常,让前端业务来处理。整个的流程它不像悲观锁,直接锁住资源,串行化处理,而是全部放开,不做任何的阻塞,大家一起来,我用版本号来控制,相比下来效率就会高很多,并发自然就能上去了。

有一个问题就是既然乐观锁这么好,又不阻塞并发又好,那干脆全部用乐观锁得了。。这个其实也是有问题的,比如说遇到了并发大的场景。就比如说电商秒杀促销,这个时候会频繁的去扣减库存,但是乐观锁版本号的机制,同一时间只能够有一个扣减成功,其他的全部失败,这个时候其他的线程就需要再次尝试,重新刷新页面,重新请求就会让系统的请求压力陡增,这个就是所谓的重置开销,那么严重的话,还会拖垮整个业务平台,所以乐观锁的使用场景,一般是访问量大,但是修改的量少的场景。这样的话既能够扛住高并发,又能够锁定资源,这个时候如果你用乐观锁就再合适不过了,当然除了这个场景乐观锁也会用在幂等的控制上面,感兴趣戳这个数据库乐观锁如何实现幂等性?

题外话:不太推荐使用悲观锁,主要原因除了性能方面,还有一些这个其他方面的因素,比如说像死锁,慢sql等。这些悲观锁是指利用数据库的锁机制来实现的悲观锁,通过select for update来锁定资源。从性能方面来讲每次都得加锁、释放锁,开销就上去了。甚至还有锁竞争,同一时间只能有一个事务在修改数据,那么其他的数据库事务全部是阻塞的状态,大家都在等待当前的事务结束,这样一来就长时间的占用着数据库的连接。我们都知道数据库连接池的数量是有限的,长时间的占用,其他的线程获取不到数据库的连接,那么上面应用层的连接就会超时,比如说像网关层的连接、负载均衡的连接都会超时,就会引起所谓的惊群效应,导致整个平台就会出现超时的现象。然后数据库平台也会频繁的报错,又各种慢sql随之而来,这些就是性能方面的了。还有就是由于悲观锁加的是行锁,基于数据库的索引来加的锁,如果遇到索引失效,甚至有些老六忘记加索引。那问题就大了。。本来加的行锁可能就立马升级为表锁了,锁住了整个表。如果涉及到多个表的关联查询,很容易就会变成死锁,如果没有外界的干预就会一直锁下去,所以不管是从性能方面还是死锁方面,都不太建议使用数据库的悲观锁,尤其是并发量大的场景,读多写少的场景,这些场景如果是用悲观锁的话,可能就会是一场灾难。。 那什么场景能用呢?一般建议是在一些要求数据强一致、并发量又不高的场景下,就可以使用,比如说像金融类的账务处理,库存扣减等等,这个时候用悲观锁再合适不过了。(完)

为什么说 Synchronized 是一个悲观锁?

ps: 如果有任何疑问,欢迎评论区给我留言


已有 0 条评论

    感谢参与互动!