在探讨悲观锁和乐观锁之前,我们先要理解什么是锁。
锁是在并发环境下,控制多个操作的顺序执行,以此来保证数据安全变动的一种机制。它是一种保证数据安全的手段,而不是特定于某项技术的,悲观锁和乐观锁亦是如此。
针对于不同的业务场景,应该选用不同的并发控制方式。所以,不要把乐观并发控制和悲观并发控制狭义的理解为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,那么说明数据被其他事务改动了,则不允许数据更新到表中,并通知用户让其重新操作。
不同于悲观锁,乐观锁是人为控制的。
三. 悲观锁和乐观锁的应用
假如一个蛋糕店里面只剩了一块小蛋糕,具体信息如下:
id | name | count |
---|---|---|
1 | 豆乳小蛋糕 | 1 |
那么这个场景下,小蛋糕的下单过程可以使用悲观锁和乐观锁进行实现。
1.悲观锁实现
比如现在有两个买家 A 和 B,A 下单前先给小蛋糕的这行数据加上悲观锁(行锁),此时这行数据只能A来操作,也就是只有 A 能买,B 想买就必须一直等待。
当 A 买好后,B 再想去买的时候会发现数量已经为0,那么 B 只能放弃购买。
2.乐观锁实现
乐观锁是通过版本号 version 来实现的,所以,我们需要给表加上 version 字段,表变动后的结构如下:
id | name | count | version |
---|---|---|---|
1 | 豆乳小蛋糕 | 1 | 0 |
在实际应用更新数据的时候,严谨的做法是带上更新前的“状态”:
A 和 B 同时将 id=1 的数据查出来,A 先买,A 将 id = 1 ,version = 0 作为条件进行数据更新,将数量 count 减一,且将版本号 version 加一,A此时就完成了商品的购买。
之后B开始买,B也将 id = 1 ,version = 0 作为条件进行数据更新,但是发现更新的数据行数为0,说明已经有人改动过数据,此时就应该提示用户重新查看最新数据再次购买了。
四. 悲观锁和乐观锁的优缺点
1. 悲观锁
优点
悲观锁利用数据库中的锁机制来实现数据变化的顺序执行。
缺点
一个事务用悲观锁对数据加锁之后,其他事务将不能对加锁的数据进行除了查询以外的所有操作,如果该事务执行时间很长,那么其他事务将一直等待,势必会影响系统的吞吐量。
2. 乐观锁
优点
乐观锁不在数据库上加锁,任何事务都可以对数据进行操作,在更新时才进行校验,这样就避免了悲观锁造成的吞吐量下降的劣势。
缺点
乐观锁是通过人为实现的,仅仅适用于自己业务中,如果有外来事务插入,那么就可能发生错误。