悲观锁和乐观锁的解读

悲观锁(Pessimistic Lock)

当我们要对一个数据库中的一条数据进行修改的时候,为了避免同时被其他人修改,最好的办法就是直接对该数据进行加锁以防止并发。这种借助数据库锁机制,在修改数据之前先锁定,再修改的方式被称之为悲观并发控制(是因为这是一种对数据的修改抱有悲观态度的并发控制方式。我们一般认为数据被并发修改的概率比较大,所以需要在修改之前先加锁。又名“悲观锁”,Pessimistic Concurrency Control,缩写“PCC”)。

  

悲观锁主要是共享锁或排他锁

  • 共享锁又称为读锁,简称S锁。顾名思义,共享锁就是多个事务对于同一数据可以共享一把锁,都能访问到数据,但是只能读不能修改。
  • 排他锁又称为写锁,简称X锁。顾名思义,排他锁就是不能与其他锁并存,如果一个事务获取了一个数据行的排他锁,其他事务就不能再获取该行的其他锁,包括共享锁和排他锁,但是获取排他锁的事务是可以对数据行读取和修改。

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

悲观锁

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

乐观锁( Optimistic Locking )

乐观锁是相对悲观锁而言的,乐观锁假设数据一般情况下不会造成冲突,所以在数据进行提交更新的时候,才会正式对数据的冲突与否进行检测,如果发现冲突了,则返回给用户错误的信息,让用户决定如何去做。

实现方式

悲观锁实现方式

悲观锁的实现,往往依靠数据库提供的锁机制。在数据库中,悲观锁的流程如下:

  1. 在对记录进行修改前,先尝试为该记录加上排他锁(exclusive locking)。
  2. 如果加锁失败,说明该记录正在被修改,那么当前查询可能要等待或者抛出异常。具体响应方式由开发者根据实际需要决定。
  3. 如果成功加锁,那么就可以对记录做修改,事务完成后就会解锁了。
  4. 期间如果有其他对该记录做修改或加排他锁的操作,都会等待我们解锁或直接抛出异常。

拿比较常用的MySql Innodb引擎举例,来说明一下在SQL中如何使用悲观锁。

要使用悲观锁,我们必须关闭MySQL数据库的自动提交属性。因为MySQL默认使用autocommit模式,也就是说,当我们执行一个更新操作后,MySQL会立刻将结果进行提交。(sql语句:set autocommit=0)

以淘宝下单过程中扣减库存的需求说明一下悲观锁的使用:

悲观锁使用

以上,在对id = 1的记录修改前,先通过for update的方式进行加锁,然后再进行修改。这就是比较典型的悲观锁策略。

如果以上修改库存的代码发生并发,同一时间只有一个线程可以开启事务并获得id=1的锁,其它的事务必须等本次事务提交之后才能执行。这样我们可以保证当前的数据不会被其它事务修改。

上面我们提到,使用select…for update会把数据给锁住,不过我们需要注意一些锁的级别,MySQL InnoDB默认行级锁。行级锁都是基于索引的,如果一条SQL语句用不到索引是不会使用行级锁的,会使用表级锁把整张表锁住,这点需要注意。

乐观锁实现方式

使用乐观锁就不需要借助数据库的锁机制了。

乐观锁的概念中其实已经阐述了它的具体实现细节。主要就是两个步骤:冲突检测和数据更新。其实现方式有一种比较典型的就是CAS(Compare and Swap)。

CAS是项乐观锁技术,当多个线程尝试使用CAS同时更新同一个变量时,只有其中一个线程能更新变量的值,而其它线程都失败,失败的线程并不会被挂起,而是被告知这次竞争中失败,并可以再次尝试。

比如前面的扣减库存问题,通过乐观锁可以实现如下:

乐观锁使用

以上,我们在更新之前,先查询一下库存表中当前库存数(quantity),然后在做update的时候,以库存数作为一个修改条件。当我们提交更新的时候,判断数据库表对应记录的当前库存数与第一次取出来的库存数进行比对,如果数据库表当前库存数与第一次取出来的库存数相等,则予以更新,否则认为是过期数据。

以上更新语句存在一个比较重要的问题,即传说中的ABA问题(指的是它值虽然和原先的值一样,但它本质上不是一样的数(可以理解为1分钟前的1和2分钟前的1))

比如说一个线程one从数据库中取出库存数3,这时候另一个线程two也从数据库中取出库存数3,并且two进行了一些操作变成了2,然后two又将库存数变成3,这时候线程one进行CAS操作发现数据库中仍然是3,然后one操作成功。尽管线程one的CAS操作成功,但是不代表这个过程就是没有问题的。

ABA

有一个比较好的办法可以解决ABA问题,那就是通过一个单独的可以顺序递增的version字段。改为以下方式即可:

ABA解决

乐观锁每次在执行数据的修改操作时,都会带上一个版本号,一旦版本号和数据的版本号一致就可以执行修改操作并对版本号执行+1操作,否则就执行失败。因为每次操作的版本号都会随之增加,所以不会出现ABA问题,因为版本号只会增加不会减少。

ABA的解决

除了version以外,还可以使用时间戳,因为时间戳天然具有顺序递增性。
以上SQL其实还是有一定的问题的,就是一旦遇上高并发的时候,就只有一个线程可以修改成功,那么就会存在大量的失败。

对于像淘宝这样的电商网站,高并发是常有的事,总让用户感知到失败显然是不合理的。所以,还是要想办法减少乐观锁的粒度的。

有一条比较好的建议,可以减小乐观锁力度,最大程度的提升吞吐率,提高并发能力!如下:

优化

以上SQL语句中,如果用户下单数为1,则通过quantity - 1 > 0的方式进行乐观锁控制。

以上update语句,在执行过程中,会在一次原子操作中自己查询一遍quantity的值,并将其扣减掉1。

高并发环境下锁粒度把控是一门重要的学问,选择一个好的锁,在保证数据安全的情况下,可以大大提升吞吐率,进而提升性能。

如何选择

在乐观锁与悲观锁的选择上面,主要看下两者的区别以及适用场景就可以了。

  1. 乐观锁并未真正加锁,效率高。一旦锁的粒度掌握不好,更新失败的概率就会比较高,容易发生业务失败。
  2. 悲观锁依赖数据库锁,效率低。更新失败的概率比较低。

随着互联网三高架构(高并发、高性能、高可用)的提出,悲观锁已经越来越少的被使用到生产环境中了,尤其是并发量比较大的业务场景。

原文地址:https://www.cnblogs.com/nyhhd/p/12702217.html

时间: 2024-08-10 02:45:43

悲观锁和乐观锁的解读的相关文章

Oracle数据库悲观锁与乐观锁详解

数据的锁定分为两种方法,第一种叫做悲观锁,第二种叫做乐观锁.什么叫悲观锁呢,悲观锁顾名思义,就是对数据的冲突采取一种悲观的态度,也就是说假设数据肯定会冲突,所以在数据开始读取的时候就把数据锁定住.而乐观锁就是认为数据一般情况下不会造成冲突,所以在数据进行提交更新的时候,才会正式对数据的冲突与否进行检测,如果发现冲突了,则让用户返回错误的信息,让用户决定如何去做. 先从悲观锁开始说.在SqlServer等其余很多数据库中,数据的锁定通常采用页级锁的方式,也就是说对一张表内的数据是一种串行化的更新插

数据库中的悲观锁和乐观锁详解

数据中的锁分为两类:悲观锁和乐观锁,锁还有表级锁.行级锁 表级锁例如: SELECT * FROM table WITH (HOLDLOCK) 其他事务可以读取表,但不能更新删除 SELECT * FROM table WITH (TABLOCKX) 其他事务不能读取表,更新和删除 行级锁例如: select * from table_name where id = 1 for update; 悲观锁(Pressimistic Locking) 对数据被外界(包括本系统当前的其他事务,以及来自

025 hibernate悲观锁、乐观锁

Hibernate谈到悲观锁.乐观锁,就要谈到数据库的并发问题,数据库的隔离级别越高它的并发性就越差 并发性:当前系统进行了序列化后,当前读取数据后,别人查询不了,看不了.称为并发性不好 数据库隔离级别:见前面章级 025-1悲观锁: 悲观锁:具有排他性(我锁住当前数据后,别人看到不此数据) 悲观锁一般由数据机制来做到的. 悲观锁的实现 通常依赖于数据库机制,在整修过程中将数据锁定,其它任何用户都不能读取或修改(如:必需我修改完之后,别人才可以修改) 悲观锁的适用场景: 悲观锁一般适合短事务比较

悲观锁及乐观锁

1.Volatile:http://www.cnblogs.com/aigongsi/archive/2012/04/01/2429166.html 2.悲观锁与乐观锁:http://www.cnblogs.com/softidea/p/5309312.html 3.乐观锁的一种实现方式:http://www.tuicool.com/articles/yiyy6bI

mysql-mysql悲观锁和乐观锁

1.mysql的四种事务隔离级别 I. 对于同时运行多个事务,当这些事务访问数据库中的相同数据时,如果没有采取必要的隔离机制,就会导致各种并发问题. (1)脏读: 对于两个事物 T1, T2, T1 读取了已经被 T2 更新但还没有被提交的字段. 之后, 若 T2 回滚, T1读取的内容就是临时且无效的. (2)不可重复读: 对于两个事物 T1, T2, T1 读取了一个字段, 然后 T2 更新了该字段. 之后, T1再次读取同一个字段, 值就不同了. (3)幻读: 对于两个事物 T1, T2,

数据库事务的悲观锁和乐观锁

转载出处:http://www.hollischuang.com/archives/934 在数据库的锁机制中介绍过,数据库管理系统(DBMS)中的并发控制的任务是确保在多个事务同时存取数据库中同一数据时不破坏事务的隔离性和统一性以及数据库的统一性. 乐观并发控制(乐观锁)和悲观并发控制(悲观锁)是并发控制主要采用的技术手段. 无论是悲观锁还是乐观锁,都是人们定义出来的概念,可以认为是一种思想.其实不仅仅是关系型数据库系统中有乐观锁和悲观锁的概念,像memcache.hibernate.tair

Java并发问题--乐观锁与悲观锁以及乐观锁的一种实现方式-CAS

首先介绍一些乐观锁与悲观锁: 悲观锁:总是假设最坏的情况,每次去拿数据的时候都认为别人会修改,所以每次在拿数据的时候都会上锁,这样别人想拿这个数据就会阻塞直到它拿到锁.传统的关系型数据库里边就用到了很多这种锁机制,比如行锁,表锁等,读锁,写锁等,都是在做操作之前先上锁.再比如Java里面的同步原语synchronized关键字的实现也是悲观锁. 乐观锁:顾名思义,就是很乐观,每次去拿数据的时候都认为别人不会修改,所以不会上锁,但是在更新的时候会判断一下在此期间别人有没有去更新这个数据,可以使用版

Hibernate 再接触 悲观锁和乐观锁

为什么取1248 二进制 CRUD 移位效率高 在并发和效率选择一个平衡点 一般不会考虑幻读 因为我们不会再一个事务里查询两次,(只能设置为seralizable) 悲观锁和乐观锁的前提是read-uncommitted 在数据库中 默认是repeatable read 悲观锁是想着总有人要更改 所以使用数据库的锁 乐观锁是在程序级别的 设置一个版本号 如果前后不一致就进行自己的操作 例子 悲观所 Acocount package com.bjsxt.hibernate; import java

zbb20180929 thread 自旋锁、阻塞锁、可重入锁、悲观锁、乐观锁、读写锁、对象锁和类锁

1.自旋锁自旋锁可以使线程在没有取得锁的时候,不被挂起,而转去执行一个空循环,(即所谓的自旋,就是自己执行空循环),若在若干个空循环后,线程如果可以获得锁,则继续执行.若线程依然不能获得锁,才会被挂起.使用自旋锁后,线程被挂起的几率相对减少,线程执行的连贯性相对加强.因此,对于那些锁竞争不是很激烈,锁占用时间很短的并发线程,具有一定的积极意义,但对于锁竞争激烈,单线程锁占用很长时间的并发程序,自旋锁在自旋等待后,往往毅然无法获得对应的锁,不仅仅白白浪费了CPU时间,最终还是免不了被挂起的操作 ,

聊聊数据库乐观锁和悲观锁,乐观锁失败后重试

在写入数据库的时候需要有锁,比如同时写入数据库的时候会出现丢数据,那么就需要锁机制. 数据锁分为乐观锁和悲观锁,那么它们使用的场景如下: 1. 乐观锁适用于写少读多的情景,因为这种乐观锁相当于JAVA的CAS,所以多条数据同时过来的时候,不用等待,可以立即进行返回. 2. 悲观锁适用于写多读少的情景,这种情况也相当于JAVA的synchronized,reentrantLock等,大量数据过来的时候,只有一条数据可以被写入,其他的数据需要等待.执行完成后下一条数据可以继续. 他们实现的方式上有所