悲观锁和乐观锁
1、什么悲观锁?
悲观锁是基于一种悲观的态度类来防止一切数据冲突,
- 以一种预防的姿态在修改数据之前把数据锁住;
- 然后再对数据进行读写,在它释放锁之前任何人都不能对其数据进行操作
- 直到前面一个人把锁释放后下一个人数据加锁才可对数据进行加锁,然后才可以对数据进行操作,一般数据库本身锁的机制都是基于悲观锁的机制实现的;
悲观锁更适用于多写少读的情况。
特点:
可以完全保证数据的独占性和正确性,因为每次请求都会先对数据进行加锁, 然后进行数据操作,最后再解锁,而加锁释放锁的过程会造成消耗,所以性能不高;
2、什么乐观锁?
乐观锁顾名思义比较乐观,只有在更新数据的时候
才会检查
这条数据是否被其他线程更新
了(这点与悲观锁一样,悲观锁是在读取数据的时候就加锁了)。
- 如果更新数据时,发现这条数据被其他线程更新了,则此次更新失败。
- 如果数据未被其他线程更新,则更新成功。
- 由于乐观锁没有了锁等待,提高了吞吐量,所以乐观锁适合多读少写的场景。
常见的乐观锁实现方式是:版本号version和CAS(compare and swap)。此处只介绍版本号方式。
特点:
乐观锁是一种并发类型的锁,其本身不对数据进行加锁通而是通过业务实现锁的功能,不对数据进行加锁就意味着允许多个请求同时访问数据,同时也省掉了对数据加锁和解锁的过程,这种方式大大的提高了数据操作的性能;
乐观锁实现形式:
表A 字段
ID | Name | version |
---|---|---|
1 | zhangsan | 1 |
1、现在有两个请求同时操作表A,操作1先查询出需要检索的数据为:
ID | Name | version |
---|---|---|
1 | zhangsan | 1 |
2、操作2也查询出需要检索的数据为:
ID | Name | version |
---|---|---|
1 | zhangsan | 1 |
3、操作1把Name字段数据修改成lisi
比操作2先提交;
此时提交操作1会把之前查询到的version与现在的数据的version进行比较,版本相同则可以提交,版本不同则视为数据过期:
update A set Name=lisi,version=version+1 where ID=#{id} and version=#{version}
此时数据库变成了如下
ID | Name | version |
---|---|---|
1 | lisi | 2 |
那么最后操作2修改了数据来提交的时候是以操作1同样的方式,当然最后肯定不能提交成功,因为他手里的版本号和现在数据库里的版本号不匹配;
总结
- 悲观锁:读取时加锁,更新完释放锁,再此过程中会造成其他线程阻塞,导致吞吐量低,适用于多写场景。
- 乐观锁:不加锁,只有在更新时验证数据是否被其他线程更新,吞吐量较高,适用于多读场景。
参考
1、https://zhuanlan.zhihu.com/p/31537871
2、https://zhuanlan.zhihu.com/p/69624585