数据库悲观锁 乐观锁
首先提到两个概念:脏读 不可重复读
脏读:上一个访问数据库的请求还没结束 下一个请求来了 就看到上一个请求进行的修改 这就属于脏读
比如: a 在提款机 看自己的账户余额 100元 然后存20元 但是还没 落库 这时候b给他打50元 b应该是 100+50 但如果隔离级别不够(读未提交)就会造成b看到的是 120+50 这时 a就产生了脏读
不可重复读:在一个访问数据库的请求进行时 其他请求进行了数据库修改 这个请求多次看数据库的数据是在变化的 这就属于不可重复读
比如 :a 在提款机 看自己的账户余额 100元 然后存20元 但是还没 落库 这时候b给他打50元 a应该看到的还是100 但是如果隔离级别不够(不可重复读)就会造成a 看到余额 由100元变成150元
更多 事务 知识请参考:
接下来看如何处理并发和同步:
同步就是加锁:悲观锁(传统的物理锁)和乐观锁
悲观锁:比较消极 每次有啥操作都上锁 一个人有动作其他人都别动 需要依靠数据库的锁机制 。
举个例子:
select * from user where name=”xcg” for update
这句sql锁定了user表的符合name = 'xcg' 的记录 ,本次事务提交前 外界无法修改这些记录。
Hibernate 的悲观锁 :query.setLockMode("user" , LockMode.UPDATE);
生成的语句后面会加上for update 子句来实现悲观锁
Hibernate 的加锁模式有:
Ø LockMode.NONE : 无锁机制。
Ø LockMode.WRITE : Hibernate 在 Insert 和 Update 记录的时候会自动获取
Ø LockMode.READ : Hibernate 在读取记录的时候会自动获取。
以上这三种锁机制一般由 Hibernate 内部使用,如 Hibernate 为了保证 Update
过程中对象不会被外界修改,会在 save 方法实现中自动为目标对象加上 WRITE 锁。
乐观锁:积极向上了很多 悲观锁 会造成很多悲观的现象:在长事务中性能开销大 大家都等着。 乐观锁 更适用于读比较多的情况,这样可以提高吞吐量
但如果是多写的情况,一般会经常产生冲突,这就会导致上层应用会不断的进行retry,这样反倒是降低了性能,所以一般多写的场景下用悲观锁就比较合适。
乐观锁的实现方式: 版本号控制 CAS算法
1.版本号:
在表加一个version字段用来表示数据被修改的次数,在读取数据的时候会读取version , 在提交更新时若version值是现在数据库的version值,就同意提交
2.CAS算法:
compare and swap (比较与交换) 是著名的不使用锁的情况下实现多线程之间的变量交换同步,也就是在没有线程阻塞的情况下 实现变量的同步。
CAS算法有三操作数:需要读写的内存之V 进行比较的值A 拟写入的新值B
当且仅当V=A时 CAS通过原子方式用B更新V
乐观锁只适用于读很多 很少写的情况