当前位置 博文首页 > 盛夏温暖流年:深入理解悲观锁和乐观锁机制

    盛夏温暖流年:深入理解悲观锁和乐观锁机制

    作者:[db:作者] 时间:2021-07-13 19:01

    在探讨悲观锁和乐观锁之前,我们先要理解什么是锁。

    锁是在并发环境下,控制多个操作的顺序执行,以此来保证数据安全变动的一种机制。它是一种保证数据安全的手段,而不是特定于某项技术的,悲观锁和乐观锁亦是如此。

    针对于不同的业务场景,应该选用不同的并发控制方式。所以,不要把乐观并发控制和悲观并发控制狭义的理解为DBMS中的概念,更不要把他们和数据中提供的锁机制(行锁、表锁、排他锁、共享锁)混为一谈。实际上,在DBMS中,悲观锁正是利用数据库本身提供的锁机制来实现的。

    下面我们就来分别介绍一下悲观锁和乐观锁。

    一. 悲观锁概念

    悲观锁(Pessimistic Concurrency Control),顾名思义,就是很悲观,具有强烈的独占和排他特性,每次获取数据的时候都认为别人会修改,所以每次都会上锁,其他任何事务都不能对该数据进行修改,只能等待锁被释放才可以执行。

    传统的关系型数据库里边就用到了很多这种锁机制,比如行锁,表锁等,读锁,写锁等,都是在做操作之前先上锁,Java中synchronized和ReentrantLock等独占锁也是悲观锁思想的实现。

    悲观锁主要分为 共享锁排他锁

    共享锁Shared Lock】:又称为读锁,简称S锁。

    是指多个事务对于同一数据可以共享一把锁,都能访问到数据,但是只能读不能修改。

    排他锁Exclusive Lock】:又称为写锁,简称X锁。

    是指不能与其他锁并存,如果一个事务获取了一个数据行的排他锁,其他事务就不能再获取该行的其他锁,包括共享锁和排他锁,但是获取排他锁的事务是可以对数据行读取和修改。

    悲观并发控制实际上是“先取锁再访问”的保守策略,为数据处理的安全提供了保证。

    但是在效率方面,处理加锁的机制会让数据库产生额外的开销,还有增加产生死锁的机会。另外还会降低并行性,一个事务如果锁定了某行数据,其他事务就必须等待该事务处理完才可以处理对应的数据。

    二. 乐观锁概念

    乐观锁(Optimistic Concurrency Control)的“乐观情绪”体现在,它认为数据的变动不会太频繁。因此,它允许多个事务同时对数据进行变动。在数据进行提交更新的时候,才会正式对数据的冲突与否进行检测,如果发现冲突了,则返回给用户错误的信息,让用户决定如何去做。

    乐观锁可以使用 版本号机制 和 CAS算法 实现,适用于多读的应用类型,可以一定程度上提高吞吐量。

    乐观不代表不负责,通常是通过在表中增加一个 版本 (version) 或 时间戳 (timestamp) 来实现,其中,版本最为常用,过程如下:

    • 事务在从数据库中取数据时,会将该数据的版本也取出来,记为 v1;

    • 当事务对数据变动完毕想要更新到表中时,将版本 v1 的数据与最新的版本 v2 的数据进行对比,如果 v1=v2,就允许事务对表中的数据进行修改;

    • 修改时 version 加1,以此来表明数据已被变动;

    • 如果,v1不等于v2,那么说明数据被其他事务改动了,则不允许数据更新到表中,并通知用户让其重新操作。

    不同于悲观锁,乐观锁是人为控制的。

    三. 悲观锁和乐观锁的应用

    假如一个蛋糕店里面只剩了一块小蛋糕,具体信息如下:

    idnamecount
    1豆乳小蛋糕1

    那么这个场景下,小蛋糕的下单过程可以使用悲观锁和乐观锁进行实现。

    1.悲观锁实现

    比如现在有两个买家 A 和 B,A 下单前先给小蛋糕的这行数据加上悲观锁(行锁),此时这行数据只能A来操作,也就是只有 A 能买,B 想买就必须一直等待。

    当 A 买好后,B 再想去买的时候会发现数量已经为0,那么 B 只能放弃购买。

    2.乐观锁实现

    乐观锁是通过版本号 version 来实现的,所以,我们需要给表加上 version 字段,表变动后的结构如下:

    idnamecountversion
    1豆乳小蛋糕10

    在实际应用更新数据的时候,严谨的做法是带上更新前的“状态”:

    A 和 B 同时将 id=1 的数据查出来,A 先买,A 将 id = 1 ,version = 0 作为条件进行数据更新,将数量 count 减一,且将版本号 version 加一,A此时就完成了商品的购买。

    之后B开始买,B也将 id = 1 ,version = 0 作为条件进行数据更新,但是发现更新的数据行数为0,说明已经有人改动过数据,此时就应该提示用户重新查看最新数据再次购买了。

    四. 悲观锁和乐观锁的优缺点

    1. 悲观锁

    优点

    悲观锁利用数据库中的锁机制来实现数据变化的顺序执行。

    缺点

    一个事务用悲观锁对数据加锁之后,其他事务将不能对加锁的数据进行除了查询以外的所有操作,如果该事务执行时间很长,那么其他事务将一直等待,势必会影响系统的吞吐量。

    2. 乐观锁

    优点

    乐观锁不在数据库上加锁,任何事务都可以对数据进行操作,在更新时才进行校验,这样就避免了悲观锁造成的吞吐量下降的劣势。

    缺点

    乐观锁是通过人为实现的,仅仅适用于自己业务中,如果有外来事务插入,那么就可能发生错误。

    cs