开门见山,先聊一聊我实际遇到的业务问题:java
在项目中有一个竞猜下注的功能,它的赔率是根据A队和B队两边的下注总金额来计算的。因而当有用户下注某一边时,两边的赔率都会进行相应的变化。mysql
反应到数据库里就是(简化版本),一我的下注,会更改数据库盘口表的几个字段:A队赔率,A队下注金额、B队赔率,B队下注金额 等等。算法
若是使用默认事务方式,就加个@Transactional 注解,会致使更新丢失的问题。(何为丢失更新:就是一个事务的更新覆盖了其它事务的更新结果。举个例子,A读到的数据为下注金额1000,对他进行计算,这时B读到的数据也是1000。A再把计算完的1200写进数据库。最后B把计算完的1100写进数据库。最终表里的下注金额就只有1100,发生了丢失更新)。若是真有高并发的状况,每秒钟几十上百我的下注的话,就必须解决此问题。sql
默认事务没法解决,固然就得寻求解决方案。这里能够采用乐观锁或悲观锁的方式。数据库
重点:每次读数据都加行锁(也称写锁、X锁),修改完后事务结束才释放。小程序
注意:mysql使用InonDB引擎时,默认增删改时都会加行锁。读不加行锁。微信小程序
# 第一步 查的时候加行锁 (注意:InnoDB只有经过索引条件检索数据才使用行级锁,不然,InnoDB将使用表锁, 也就是说,InnoDB的行锁是基于索引的!)
SELECT * FROM table_name WHERE xxx FOR UPDATE;
# 第二步 逻辑处理完后更新数据
UPDATE xxx...
复制代码
@Query(value = "SELECT * FROM guessing_handicap WHERE handicap_id = ?1 FOR UPDATE", nativeQuery = true)
GuessingHandicap getBet(Integer id);
复制代码
实现起来十分简单,概念的理解放在后面写。性能优化
乐观锁的实现通常会使用版本号机制或CAS算法bash
int count = 0; // 计数重复次数,暂定10次
while (count < 10) {
count++;
// 先读取数据,保存版本号
GuessingHandicap handicap = guessingHandicapDao.getBet(id);
Integer version = handicap.getVersion();
// 进行数据的处理
// ...
// 将处理完的结果写回数据库
Integer rows = guessingHandicapDao.updateHandicap(...);
if (rows == 0) {
continue;
}
// ...
}
throw new ValidationException("下注失败");
复制代码
CAS概念略复杂,举个简单的实现方式:仍是盘口表,我读数据的时候读到了该条记录的下注金额,赔率,将其数据暂时保存。处理完逻辑写回去时,可用 update xxx set odds = 新赔率 where odds = 原来赔率微信
- CAS算法也有缺点,最明显且容易理解的就是,会致使 ABA 问题。
- 若是一个变量V初次读取的时候是A值,而且在准备赋值的时候检查到它仍然是A值,
那咱们就能说明它的值没有被其余线程修改过了吗?很明显是不能的,由于在这段时间它的值可能被改成其余值,
而后又改回A,那CAS操做就会误认为它历来没有被修改过。这个问题被称为CAS操做的 "ABA"问题。
复制代码
因此乐观锁建议使用版本号机制。就加个字段,简单轻松。
从上面对两种锁的介绍,咱们知道两种锁各有优缺点,不可认为一种好于另外一种,像乐观锁适用于写比较少的状况下(多读场景),即冲突真的不多发生的时候,这样能够省去了锁的开销,加大了系统的整个吞吐量。但若是是多写的状况,通常会常常产生冲突,这就会致使上层应用会不断的进行retry,这样反却是下降了性能,因此通常多写的场景下用悲观锁就比较合适。
记住结论,即:乐观锁适用于写比较少的状况(多读场景);悲观锁适用于多写场景。
乐观锁对应于生活中乐观的人老是想着事情往好的方向发展,悲观锁对应于生活中悲观的人老是想着事情往坏的方向发展。这两种人各有优缺点,不能不以场景而定说一种人好于另一种人。
老是假设最坏的状况,每次去拿数据的时候都认为别人会修改,因此每次在拿数据的时候都会上锁,这样别人想拿这个数据就会阻塞直到它拿到锁(共享资源每次只给一个线程使用,其它线程阻塞,用完后再把资源转让给其它线程)。传统的关系型数据库里边就用到了不少这种锁机制,好比行锁,表锁等,读锁,写锁等,都是在作操做以前先上锁。Java中synchronized和ReentrantLock等独占锁就是悲观锁思想的实现。
老是假设最好的状况,每次去拿数据的时候都认为别人不会修改,因此不会上锁,可是在更新的时候会判断一下在此期间别人有没有去更新这个数据,可使用版本号机制和CAS算法实现。乐观锁适用于多读的应用类型,这样能够提升吞吐量,像数据库提供的相似于write_condition机制,其实都是提供的乐观锁。在Java中java.util.concurrent.atomic包下面的原子变量类就是使用了乐观锁的一种实现方式CAS实现的。
从锁的粒度,咱们能够将数据库的锁分红两大类: 表锁和行锁,
表锁又分为表读锁和表写锁,
行锁又分为共享锁和排他锁,而共享锁、排他所又有其余别名,其实只是叫法的不一样而已
为了容许行锁和表锁共存,实现多粒度锁机制,InnoDB还有两种内部使用的意向锁(Intention Locks),这两种意向锁都是表锁:
网上的各类资料里众说纷纭:
“只读事务”并非一个强制选项,它只是一个“暗示”,提示数据库驱动程序和数据库系统,这个事务并不包含更改数据的操做,那么JDBC驱动程序和数据库就有可能根据这种状况对该事务进行一些特定的优化,比方说不安排相应的数据库锁,以减轻事务对数据库的压力,毕竟事务也是要消耗数据库的资源的。 所以,“只读事务”仅仅是一个性能优化的推荐配置而已,并不是强制你要这样作不可。
@Transactional(readOnly = true)
复制代码
隆鹏
芦苇科技Java开发工程师
芦苇科技-广州专业软件外包服务公司
提供微信小程序、APP应用研发、UI设计等专业服务,专一于互联网产品咨询、品牌设计、技术研发等领域、
访问 www.talkmoney.cn 了解更多
万能说明书 | 早起日记Lite | 凹凸壁纸 | 言财