版权声明:本文由 Hov 所有,发布于 http://chenhy.com ,转载请注明出处。
0 前言
乐观锁、悲观锁是人们定义出来的概念,可认为是一种思想。也就是说其不依赖于某个特定的数据库,不仅仅是关系型数据库系统有乐观锁、悲观锁,像 memcache 、 Hibernate 等也有类似的概念。乐观锁、悲观锁是用来保证数据并发安全的方法。
1 悲观锁
1.1 简介
悲观锁并发控制方案,在各种情况下都上锁。上锁之后,就只有一个线程可以操作这一数据。
1.2 实例
牙膏库存为 100 件,用户 A (线程 A )和用户 B (线程 B )同时购买一件牙膏。
- 线程 A 读取牙膏库存( 100 件,并对数据加锁),此时线程 B 无法读取牙膏库存数据;
- 线程 A 将牙膏库存减 1 ,库存为 100-1=99 件;
- 线程 A 将数据更新至数据库,数据库牙膏库存为 99 件,数据解锁;
- 因为数据解锁了,线程 B 可以读取牙膏库存数据( 99 件,并对数据加锁);
- 线程 B 将牙膏库存减 1 ,库存为 99-1=98 件;
- 线程 B 将数据更新至数据库,数据库中牙膏库存变为 98 件,数据解锁。
注:执行顺序为绿色 → 蓝色 → 红色
1.3 优缺点
优点:
- 操作方便,直接加锁;
- 对程序透明,无需额外操作。
缺点:
- 并发能力低,同一时间只能有一个线程操作数据。
2 乐观锁
2.1 简介
乐观锁通俗的理解就是,数据有一个版本号,第一次读的时候将获取数据的版本号;当需要对数据进行更新时,需要检查数据库中的版本号与第一次获取的版本号是否一致。如果一致则更新数据,否则不更新。也就是说,要保证数据在中间没被修改过。
2.2 实例
牙膏库存为 100 件,用户 A (线程 A )和用户 B (线程 B )同时购买一件牙膏。
- 线程 A 读取牙膏库存( 100 件,版本号为 1 ,不加锁),线程 B 读取牙膏库存( 100 件,版本号为 1 ,不加锁);
- 线程 A 将库存减 1 , 100-1=99 件;线程 B 将库存减 1 , 100-1=99 件(注意还没更新到牙膏库存);
- 因为线程 A 、线程 B 总有一个线程必先执行,假设是线程 A 。线程 A 将库存减 1 后(库存为 100-1=99 件),通过本地版本号与数据库版本号比对(都为 1 ),因为版本号一致,所以更新牙膏库存(牙膏库存变为 99 件,版本号变为 2 ),线程 A 执行完毕;
- 线程 B 将库存减 1 后(库存为 100-1=99 件),通过本地版本号(为 1 )与数据库版本号(为 2 )比对,因为版本号不一致,所以不更新牙膏库存,线程 B 重新读取牙膏库存(库存为 99 ,版本号为 2 ),重复执行。
- 线程 B 将库存减 1 后(库存为 99-1=98 件),通过本地版本号(为 2 )与数据库版本号(为 2 )对比,因为版本号一致,所以更新牙膏库存(牙膏库存变为 98 件,版本号变为 3 ),线程 B 执行完毕。
注:执行顺序为绿色 → 蓝色 → 红色
2.3 优缺点
优点:
- 并发能力好;
- 不给数据加锁。
缺点:
- 繁琐,每次更新都要比较版本号;
- 可能需要重复加载数据。