转载https://www.cnblogs.com/zhiqian-ali/p/6200874.html
【MySQL】悲观锁&乐观锁
悲观锁与乐观锁是两种常见的资源并发锁设计思路,也是并发编程中一个非常基础的概念。本文将对这两种常见的锁机制在数据库数据上的实现进行比较系统的介绍。
悲观锁(Pessimistic Lock)
悲观锁的特点是先获取锁,再进行业务操作,即“悲观”的认为获取锁是非常有可能失败的,因此要先确保获取锁成功再进行业务操作。通常所说的“一锁二查三更新”即指的是使用悲观锁。通常来讲在数据库上的悲观锁需要数据库本身提供支持,即通过常用的select … for update操作来实现悲观锁。当数据库执行select for update时会获取被select中的数据行的行锁,因此其他并发执行的select for update如果试图选中同一行则会发生排斥(需要等待行锁被释放),因此达到锁的效果。select for update获取的行锁会在当前事务结束时自动释放,因此必须在事务中使用。
这里需要注意的一点是不同的数据库对select for update的实现和支持都是有所区别的,例如oracle支持select for update no wait,表示如果拿不到锁立刻报错,而不是等待,mysql就没有no wait这个选项。另外mysql还有个问题是select for update语句执行中所有扫描过的行都会被锁上,这一点很容易造成问题。因此如果在mysql中用悲观锁务必要确定走了索引,而不是全表扫描。
乐观锁(Optimistic Lock)
乐观锁的特点先进行业务操作,不到万不得已不去拿锁。即“乐观”的认为拿锁多半是会成功的,因此在进行完业务操作需要实际更新数据的最后一步再去拿一下锁就好。
乐观锁在数据库上的实现完全是逻辑的,不需要数据库提供特殊的支持。一般的做法是在需要锁的数据上增加一个版本号,或者时间戳,然后按照如下方式实现:
复制代码
- SELECT data AS old_data, version AS old_version FROM …;
- 根据获取的数据进行业务操作,得到new_data和new_version
- UPDATE SET data = new_data, version = new_version WHERE version = old_version
if (updated row > 0) {
// 乐观锁获取成功,操作完成
} else {
// 乐观锁获取失败,回滚并重试
}
复制代码
乐观锁是否在事务中其实都是无所谓的,其底层机制是这样:在数据库内部update同一行的时候是不允许并发的,即数据库每次执行一条update语句时会获取被update行的写锁,直到这一行被成功更新后才释放。因此在业务操作进行前获取需要锁的数据的当前版本号,然后实际更新数据时再次对比版本号确认与之前获取的相同,并更新版本号,即可确认这之间没有发生并发的修改。如果更新失败即可认为老版本的数据已经被并发修改掉而不存在了,此时认为获取锁失败,需要回滚整个业务操作并可根据需要重试整个过程。
总结
乐观锁在不发生取锁失败的情况下开销比悲观锁小,但是一旦发生失败回滚开销则比较大,因此适合用在取锁失败概率比较小的场景,可以提升系统并发性能
乐观锁还适用于一些比较特殊的场景,例如在业务操作过程中无法和数据库保持连接等悲观锁无法适用的地方
例子
故事背景
A员工和B员工,是某电商平台的两位小员工(线程)。
某日A接到通知,卖出巧克力30块,请对库存减少30。A便去库存表取出巧克力的数据,嗯,库存还剩1000。1000 - 30 = 970。把库存改为970就好了。
A改好了数据,准备放入库存表中。
此时,B也接到通知,卖出巧克力50块,请对库存减少50。B便去库存表取出巧克力的数据,嗯,库存还剩1000(此时A还未更新数据到库存表中),1000 - 50 = 950。
这时候,B更新了数据,覆盖掉A刚才更新的结果。于是,卖出了80块巧克力,库存只减少了50。
这是并发场景中常见的因未加锁而导致出现脏数据的情况。
有什么解决办法呢?
解决办法一:悲观锁
A对B说,以后我们给要操作的数据加个锁吧,这样子就能保证只有一个人在操作了。
于是,又遇到了这种场景,只是货物从巧克力变成了士力架。
A收到改库存数据的消息,立刻取出数据,加了个锁。然后开始进行加减乘除操作。
B收到消息,来到了库存表,发现自己想更新的数据,有个锁在上面,自己没办法取到。“嗯,说明现在有人在操作这条数据,我不能再犯上一次的错误了,等等就好了”。
于是B就去泡茶吹水去了。
过了一会儿,来看,发现还是有个锁(A又收到了消息),于是B又去吹水了。
第三次过来看,没锁了,B便执行了自己的操作。
然后B就被老板骂了,因为同样的一个下午,A改了20条数据,B只改了3条。
(悲观锁的机制,容易导致某个请求的数据库操作等待耗时过长)
解决办法二:乐观锁
B对A说,以后我们取出数据的时候,除了库存数据,再加个版本号数据吧。下次操作的时候,如果版本号数据与一开始取出的结果不同,便说明有人更改了这条数据,我们再从头操作一下就好了。
某一天,A收到了更改库存的消息,取出了巧克力的库存数据,500条,同时看了一下它的版本号为1。然后A去执行自己的操作了。
这时候,B也取出了巧克力库存数据,500条。看了一下数据的版本号为1,然后B去执行自己的操作了。
A先执行完了,回过头去看巧克力的数据的版本号:”嗯,还是1“。于是A去更新库存数据,巧克力库存为470,同时把版本号改为2。
B也执行完了,回过头去看巧克力的数据版本号:”我去,变成2了“。B无奈地摇了摇头,又重新取出新的数据470,去做加减乘除。执行完后回头看:”嗯,还是2,和这次操作一开始的数据一样,于是B也更新了这条数据“。
老板看了很开心,B和A都忙得不亦乐乎,没人在吹水。毕竟他最看不顺眼有人在上班的时候吹水了。
(乐观锁的机制,容易出现重试)
作者:Nucky_
来源:CSDN
原文:https://blog.csdn.net/hhooong/article/details/79052462
版权声明:本文为博主原创文章,转载请附上博文链接!