悲观锁
简单理解下悲观锁:当一个事务锁定了一些数据之后,只有当当前锁提交了事务,释放了锁,其他事务才能获得锁并执行操作。
使用方式如下:
首先要关闭MySQL的自动提交:set autocommit = 0;
bigen --开启事务 select id, total, front, end from price where id=1 for update insert into price values(?,?,?,?,?) commit --提交事务
这里使用select for update
的方式利用数据库开启了悲观锁,锁定了id=1的这条数据(注意:这里除非是使用了索引会启用行级锁,不然是会使用表锁,将整张表都锁住。
)。之后使用commit
提交事务并释放锁,这样下一个线程过来拿到的就是正确的数据。
悲观锁一般是用于并发不是很高,并且不允许脏读等情况。但是对数据库资源消耗较大。
乐观锁
那么有没有性能好,支持的并发也更多的方式呢?
那就是乐观锁。
乐观锁是首先假设数据冲突很少,只有在数据提交修改的时候才进行校验,如果冲突了则不会进行更新。
通常的实现方式增加一个version
字段,为每一条数据加上版本。每次更新的时候version+1
,并且更新时候带上版本号。实现方式如下:
新建了一张price_version
表:
CREATE TABLE `price_version` ( `id` int(11) NOT NULL AUTO_INCREMENT COMMENT '主键', `total` decimal(12,2) DEFAULT '0.00' COMMENT '总值', `front` decimal(12,2) DEFAULT '0.00' COMMENT '消费前', `end` decimal(12,2) DEFAULT '0.00' COMMENT '消费后', `version` int(11) DEFAULT '0' COMMENT '并发版本控制', PRIMARY KEY (`id`) ) ENGINE=InnoDB AUTO_INCREMENT=1268 DEFAULT CHARSET=utf8
更新数据的SQL:
<update id="updateByVersion" parameterType="com.crossoverJie.pojo.PriceVersion"> UPDATE price_version SET front = #{front,jdbcType=DECIMAL}, version= version + 1 WHERE id = #{id,jdbcType=INTEGER} AND version = #{version,jdbcType=INTEGER} </update>
调用方式:
/** * 线程池,乐观锁 * @param redisContentReq * @return */ @RequestMapping(value = "/threadPriceVersion",method = RequestMethod.POST) @ResponseBody public BaseResponse<NULLBody> threadPriceVersion(@RequestBody RedisContentReq redisContentReq){ BaseResponse<NULLBody> response = new BaseResponse<NULLBody>() ; try { for (int i=0 ;i<3 ;i++){ Thread t = new Thread(new Runnable() { @Override public void run() { PriceVersion priceVersion = priceVersionMapper.selectByPrimaryKey(1); int ron = new Random().nextInt(20); logger.info("本次消费="+ron); priceVersion.setFront(new BigDecimal(ron)); int count = priceVersionMapper.updateByVersion(priceVersion); if (count == 0){ logger.error("更新失败"); }else { logger.info("更新成功"); } } }); config.submit(t); } response.setReqNo(redisContentReq.getReqNo()); response.setCode(StatusEnum.SUCCESS.getCode()); response.setMessage(StatusEnum.SUCCESS.getMessage()); }catch (Exception e){ logger.error("system error",e); response.setReqNo(response.getReqNo()); response.setCode(StatusEnum.FAIL.getCode()); response.setMessage(StatusEnum.FAIL.getMessage()); } return response ; }
处理逻辑:开了三个线程生成了20以内的随机数更新到front
字段。
当调用该接口时日志如下:
03.jpg
可以看到线程1、4、5分别生成了15,2,11三个随机数。最后线程4、5都更新失败了,只有线程1更新成功了。
查看数据库:
04.jpg
发现也确实是更新的15。
乐观锁在实际应用相对较多,它可以提供更好的并发访问,并且数据库开销较少,但是有可能存在脏读的情况。
总结
以上两种各有优劣,大家可以根据具体的业务场景来判断具体使用哪种方式来保证数据的一致性。
个人博客地址:crossoverjie.top。