悲观锁&乐观锁

最近意外发现之前对悲观锁乐观锁的理解有误,所以重新学习了一下。

1.悲观锁

悲观锁介绍(百科):

悲观锁,正如其名,它指的是对数据被外界(包括本系统当前的其他事务,以及来自外部系统的事务处理)修改持保守态度,因此,在整个数据处理过程中,将数据处于锁定状态。悲观锁的实现,往往依靠数据库提供的锁机制(也只有数据库层提供的锁机制才能真正保证数据访问的排他性,否则,即使在本系统中实现了加锁机制,也无法保证外部系统不会修改数据)。

使用场景举例:以MySQL InnoDB为例

商品goods表中有一个字段status,status为1代表商品未被下单,status为2代表商品已经被下单,那么我们对某个商品下单时必须确保该商品status为1。假设商品的id为1。

1如果不采用锁,那么操作方法如下:

//1.查询出商品信息

select status from t_goods where id=1;

//2.根据商品信息生成订单

insert into t_orders (id,goods_id) values (null,1);

//3.修改商品status为2

update t_goods set status=2;

上面这种场景在高并发访问的情况下很可能会出现问题。

前面已经提到,只有当goods status为1时才能对该商品下单,上面第一步操作中,查询出来的商品status为1。但是当我们执行第三步Update操作的时候,有可能出现其他人先一步对商品下单把goods status修改为2了,但是我们并不知道数据已经被修改了,这样就可能造成同一个商品被下单2次,使得数据不一致。所以说这种方式是不安全的。

2使用悲观锁来实现:

在上面的场景中,商品信息从查询出来到修改,中间有一个处理订单的过程,使用悲观锁的原理就是,当我们在查询出goods信息后就把当前的数据锁定,直到我们修改完毕后再解锁。那么在这个过程中,因为goods被锁定了,就不会出现有第三者来对其进行修改了。

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

我们可以使用命令设置MySQL为非autocommit模式:

set autocommit=0;

设置完autocommit后,我们就可以执行我们的正常业务了。具体如下:

//0.开始事务

begin;/begin work;/start transaction; (三者选一就可以)

//1.查询出商品信息

select status from t_goods where id=1 for update;

//2.根据商品信息生成订单

insert into t_orders (id,goods_id) values (null,1);

//3.修改商品status为2

update t_goods set status=2;

//4.提交事务

commit;/commit work;

注:上面的begin/commit为事务的开始和结束,因为在前一步我们关闭了mysql的autocommit,所以需要手动控制事务的提交,在这里就不细表了。

上面的第一步我们执行了一次查询操作:select status from t_goods where id=1 for update;

与普通查询不一样的是,我们使用了select…for update的方式,这样就通过数据库实现了悲观锁。此时在t_goods表中,id为1的 那条数据就被我们锁定了,其它的事务必须等本次事务提交之后才能执行。这样我们可以保证当前的数据不会被其它事务修改。

注:需要注意的是,在事务中,只有SELECT ... FOR UPDATE 或LOCK IN SHARE MODE 同一笔数据时会等待其它事务结束后才执行,一般SELECT ... 则不受此影响。拿上面的实例来说,当我执行select status from t_goods where id=1 for update;后。我在另外的事务中如果再次执行select status from t_goods where id=1 for update;则第二个事务会一直等待第一个事务的提交,此时第二个查询处于阻塞的状态,但是如果我是在第二个事务中执行select status from t_goods where id=1;则能正常查询出数据,不会受第一个事务的影响。

补充:MySQL select…for update的Row Lock与Table Lock

上面我们提到,使用select…for update会把数据给锁住,不过我们需要注意一些锁的级别,MySQL InnoDB默认Row-Level Lock,所以只有「明确」地指定主键,MySQL 才会执行Row lock (只锁住被选取的数据) ,否则MySQL 将会执行Table Lock (将整个数据表单给锁住)。

举例说明:

数据库表t_goods,包括id,status,name三个字段,id为主键,数据库中记录如下;

Sql代码  

  1. mysql> select * from t_goods;
  2. +----+--------+------+
  3. | id | status | name |
  4. +----+--------+------+
  5. |  1 |      1 | 道具 |
  6. |  2 |      1 | 装备 |
  7. +----+--------+------+
  8. 2 rows in set
  9. mysql>

注:为了测试数据库锁,我使用两个console来模拟不同的事务操作,分别用console1、console2来表示。

例1: (明确指定主键,并且有此数据,row lock)

console1:查询出结果,但是把该条数据锁定了

Sql代码

  1. mysql> select * from t_goods where id=1 for update;
  2. +----+--------+------+
  3. | id | status | name |
  4. +----+--------+------+
  5. |  1 |      1 | 道具 |
  6. +----+--------+------+
  7. 1 row in set
  8. mysql>

console2:查询被阻塞

Sql代码

  1. mysql> select * from t_goods where id=1 for update;

console2:如果console1长时间未提交,则会报错

Sql代码

  1. mysql> select * from t_goods where id=1 for update;
  2. ERROR 1205 : Lock wait timeout exceeded; try restarting transaction

例2: (明确指定主键,若查无此数据,无lock)

console1:查询结果为空

Sql代码

  1. mysql> select * from t_goods where id=3 for update;
  2. Empty set

console2:查询结果为空,查询无阻塞,说明console1没有对数据执行锁定

Sql代码

  1. mysql> select * from t_goods where id=3 for update;
  2. Empty set

例3: (无主键,table lock)

console1:查询name=道具 的数据,查询正常

Sql代码

  1. mysql> select * from t_goods where name=‘道具‘ for update;
  2. +----+--------+------+
  3. | id | status | name |
  4. +----+--------+------+
  5. |  1 |      1 | 道具 |
  6. +----+--------+------+
  7. 1 row in set
  8. mysql>

console2:查询name=装备 的数据,查询阻塞,说明console1把表给锁住了

Sql代码

  1. mysql> select * from t_goods where name=‘装备‘ for update;

console2:若console1长时间未提交,则查询返回为空

Sql代码

  1. mysql> select * from t_goods where name=‘装备‘ for update;
  2. Query OK, -1 rows affected

例4: (主键不明确,table lock)

console1:查询正常

Sql代码

  1. mysql> begin;
  2. Query OK, 0 rows affected
  3. mysql> select * from t_goods where id>0 for update;
  4. +----+--------+------+
  5. | id | status | name |
  6. +----+--------+------+
  7. |  1 |      1 | 道具 |
  8. |  2 |      1 | 装备 |
  9. +----+--------+------+
  10. 2 rows in set
  11. mysql>

console2:查询被阻塞,说明console1把表给锁住了

Sql代码

  1. mysql> select * from t_goods where id>1 for update;

例5: (主键不明确,table lock)

console1:

Sql代码

  1. mysql> begin;
  2. Query OK, 0 rows affected
  3. mysql> select * from t_goods where id<>1 for update;
  4. +----+--------+------+
  5. | id | status | name |
  6. +----+--------+------+
  7. |  2 |      1 | 装备 |
  8. +----+--------+------+
  9. 1 row in set
  10. mysql>

console2:查询被阻塞,说明console1把表给锁住了

Sql代码

  1. mysql> select * from t_goods where id<>2 for update;

console1:提交事务

Sql代码

  1. mysql> commit;
  2. Query OK, 0 rows affected

console2:console1事务提交后,console2查询结果正常

Sql代码

  1. mysql> select * from t_goods where id<>2 for update;
  2. +----+--------+------+
  3. | id | status | name |
  4. +----+--------+------+
  5. |  1 |      1 | 道具 |
  6. +----+--------+------+
  7. 1 row in set
  8. mysql>

以上就是关于数据库主键对MySQL锁级别的影响实例,需要注意的是,除了主键外,使用索引也会影响数据库的锁定级别

举例:

我们修改t_goods表,给status字段创建一个索引

修改id为2的数据的status为2,此时表中数据为:

Sql代码

  1. mysql> select * from t_goods;
  2. +----+--------+------+
  3. | id | status | name |
  4. +----+--------+------+
  5. |  1 |      1 | 道具 |
  6. |  2 |      2 | 装备 |
  7. +----+--------+------+
  8. 2 rows in set
  9. mysql>

例6: (明确指定索引,并且有此数据,row lock)

console1:

Sql代码

  1. mysql> select * from t_goods where status=1 for update;
  2. +----+--------+------+
  3. | id | status | name |
  4. +----+--------+------+
  5. |  1 |      1 | 道具 |
  6. +----+--------+------+
  7. 1 row in set
  8. mysql>

console2:查询status=1的数据时阻塞,超时后返回为空,说明数据被console1锁定了

Sql代码

  1. mysql> select * from t_goods where status=1 for update;
  2. Query OK, -1 rows affected

console2:查询status=2的数据,能正常查询,说明console1只锁住了行,未锁表

Sql代码

  1. mysql> select * from t_goods where status=2 for update;
  2. +----+--------+------+
  3. | id | status | name |
  4. +----+--------+------+
  5. |  2 |      2 | 装备 |
  6. +----+--------+------+
  7. 1 row in set
  8. mysql>

例7: (明确指定索引,若查无此数据,无lock)

console1:查询status=3的数据,返回空数据

Sql代码

  1. mysql> select * from t_goods where status=3 for update;
  2. Empty set

console2:查询status=3的数据,返回空数据

Sql代码

  1. mysql> select * from t_goods where status=3 for update;
  2. Empty set

==================================================================================================================================

2. 乐观锁

乐观锁( Optimistic Locking ) 相对悲观锁而言,乐观锁假设认为数据一般情况下不会造成冲突,所以在数据进行提交更新的时候,才会正式对数据的冲突与否进行检测,如果发现冲突了,则让返回用户错误的信息,让用户决定如何去做。那么我们如何实现乐观锁呢,一般来说有以下2种方式:

1.使用数据版本(Version)记录机制实现,这是乐观锁最常用的一种实现方式。何谓数据版本?即为数据增加一个版本标识,一般是通过为数据库表增加一个数字类型的 “version” 字段来实现。当读取数据时,将version字段的值一同读出,数据每更新一次,对此version值加一。当我们提交更新的时候,判断数据库表对应记录的当前版本信息与第一次取出来的version值进行比对,如果数据库表当前版本号与第一次取出来的version值相等,则予以更新,否则认为是过期数据。用下面的一张图来说明:

如上图所示,如果更新操作顺序执行,则数据的版本(version)依次递增,不会产生冲突。但是如果发生有不同的业务操作对同一版本的数据进行修改,那么,先提交的操作(图中B)会把数据version更新为2,当A在B之后提交更新时发现数据的version已经被修改了,那么A的更新操作会失败。

2.乐观锁定的第二种实现方式和第一种差不多,同样是在需要乐观锁控制的table中增加一个字段,名称无所谓,字段类型使用时间戳(timestamp), 和上面的version类似,也是在更新提交的时候检查当前数据库中数据的时间戳和自己更新前取到的时间戳进行对比,如果一致则OK,否则就是版本冲突。

时间: 2024-10-21 22:29:00

悲观锁&乐观锁的相关文章

黑马day11 悲观锁&amp;乐观锁

悲观锁:悲观锁悲观的认为每一次操作都会造成更新丢失问题,在每次查询时就加上排他锁 乐观锁:乐观锁会乐观的认为每次查询都不会造成更新丢失.利用一个版本字段进行控制 查询非常多,修改非常少,使用乐观锁 修改非常多,查询非常少,使用悲观锁 第一张图的解释: 小zhang想在一个游戏网站买装备,此时游戏网站会去重定向到银行(假设是建设银行),然后银行再重定向会这个游戏网站. 但是小zhang点击充值的时候由于网页很慢,点击了好几下.....这个时候为了防止银行的钱过多的充值到小zhang的用户,银行会

最全Java锁详解:独享锁/共享锁+公平锁/非公平锁+乐观锁/悲观锁

在Java并发场景中,会涉及到各种各样的锁如公平锁,乐观锁,悲观锁等等,这篇文章介绍各种锁的分类: 公平锁/非公平锁 可重入锁 独享锁/共享锁 乐观锁/悲观锁 分段锁 自旋锁 01.乐观锁 vs 悲观锁 乐观锁与悲观锁是一种广义上的概念,体现了看待线程同步的不同角度,在Java和数据库中都有此概念对应的实际应用. 1.乐观锁 顾名思义,就是很乐观,每次去拿数据的时候都认为别人不会修改,所以不会上锁,但是在更新的时候会判断一下在此期间别人有没有去更新这个数据,可以使用版本号等机制. 乐观锁适用于多

Hibernate 悲观锁,乐观锁

业务逻辑的实现过程中,往往需要保证数据访问的排他性.因此,我们就需要通过一些机制来保证这些数据在某个操作过程中不会被外界修改,这样的机制,在这里,也就是所谓的"锁",即给我们选定的目标数据上锁,使其无法被其它程序修改. Hibernate 支持两种锁机制: 1. 悲观锁(Pessimistic Locking) 从加载对象就开始锁定.修改过程中一直是锁.直到事务commit()提交后再解锁. session.load(Info.class,"p003",LockOp

【MySQL】悲观锁&amp;乐观锁

悲观锁与乐观锁是两种常见的资源并发锁设计思路,也是并发编程中一个非常基础的概念.本文将对这两种常见的锁机制在数据库数据上的实现进行比较系统的介绍. 悲观锁(Pessimistic Lock) 悲观锁的特点是先获取锁,再进行业务操作,即"悲观"的认为获取锁是非常有可能失败的,因此要先确保获取锁成功再进行业务操作.通常所说的"一锁二查三更新"即指的是使用悲观锁.通常来讲在数据库上的悲观锁需要数据库本身提供支持,即通过常用的select - for update操作来实现

多线程之 悲观锁,乐观锁

1.悲观锁,正如其名,它指的是对数据被外界(包括本系统当前的其他事务,以及来自外部系统的事务处理)修改持保守态度,因此,在整个数据处理过程中,将数据处于锁定状态.悲观锁的实现,往往依靠数据库提供的锁机制(也只有数据库层提供的锁机制才能真正保证数据访问的排他性,否则,即使在本系统中实现了加锁机制,也无法保证外部系 统不会修改数据). 数据库锁机制: 1        未提交读(read uncommitted) 2        提交读(read committed) 3        重复读(r

谈谈mysql的悲观和乐观锁

悲观锁与乐观锁是两种常见的资源并发锁设计思路,也是并发编程中一个非常基础的概念.之前有写过一篇文章关于并发的处理思路和解决方案,这里我单独将对这两种常见的锁机制在数据库数据上的实现进行比较系统的介绍一次吧. 悲观锁(Pessimistic Lock) 悲观锁的特点是先获取锁,再进行业务操作,即"悲观"的认为获取锁是非常有可能失败的,因此要先确保获取锁成功再进行业务操作.通常所说的"一锁二查三更新"即指的是使用悲观锁.通常来讲在数据库上的悲观锁需要数据库本身提供支持,

innodb 悲观锁,乐观锁

转 http://www.cnblogs.com/chenwenbiao/archive/2012/06/06/2537508.html CREATE TABLE `products` ( `id` int(11) NOT NULL AUTO_INCREMENT, `name` varchar(256) NOT NULL, `quantity` int NOT NULL, `cityid` varchar(20) DEFAULT NULL, PRIMARY KEY (`id`), KEY `id

悲观锁乐观锁简单整理

一:介绍 悲观锁,正如其名,具有强烈的独占和排他特性.它指的是对数据被外界(包括本系统当前的其他事务,以及来自外部系统的事务处理)修改持保守态度,因此,在整个数据处理过程中,将数据处于锁定状态.悲观锁的实现,往往依靠数据库提供的锁机制(也只有数据库层提供的锁机制才能真正保证数据访问的排他性,否则,即使在本系统中实现了加锁机制,也无法保证外部系统不会修改数据).乐观锁机制采取了更加宽松的加锁机制.悲观锁大多数情况下依靠数据库的锁机制实现,以保证操作最大程度的独占性.但随之而来的就是数据库 性能的大

一句话撸完重量级锁、自旋锁、轻量级锁、偏向锁、悲观、乐观锁等

重量级锁?自旋锁?自适应自旋锁?轻量级锁?偏向锁?悲观锁?乐观锁?执行一个方法咋这么辛苦,到处都是锁. 今天这篇文章,给大家普及下这些锁究竟是啥,他们的由来,他们之间有啥关系,有啥区别. 重量级锁 如果你学过多线程,那么你肯定知道锁这个东西,至于为什么需要锁,我就不给你普及了,就当做你是已经懂的了. 我们知道,我们要进入一个同步.线程安全的方法时,是需要先获得这个方法的锁的,退出这个方法时,则会释放锁.如果获取不到这个锁的话,意味着有别的线程在执行这个方法,这时我们就会马上进入阻塞的状态,等待那