2.1K Star 13.9K Fork 5.8K

GVP小柒2012 / spring-boot-seckill

 / 详情

秒杀而防止超卖解决?

待办的
创建于  
2022-03-29 16:08

因为spring执行事务是在方法结束后提交的。所有会存在一段锁释放之后,事务提取之前的状态。数据库默认是支持可重复读的,所以有可能读到过时信息,导致最后出错。
原代码如下

public Result  startSeckilLock(long seckillId, long userId) {
		lock.lock();
		try {
			/**
			 * 1)这里、不清楚为啥、总是会被超卖101、难道锁不起作用、lock是同一个对象
			 * 2)来自热心网友 zoain 的细心测试思考、然后自己总结了一下,事物未提交之前,锁已经释放(事物提交是在整个方法执行完),导致另一个事物读取到了这个事物未提交的数据,也就是传说中的脏读。建议锁上移
			 * 3)给自己留个坑思考:为什么分布式锁(zk和redis)没有问题?(事实是有问题的,由于redis释放锁需要远程通信,不那么明显而已)
			 * 4)2018年12月35日,更正一下,之前的解释(脏读)可能给大家一些误导,数据库默认的事务隔离级别为 可重复读(repeatable-read),也就不可能出现脏读
			 * 哪个这个级别是只能是幻读了?分析一下:幻读侧重于新增或删除,这里显然不是,那这里到底是什么,给各位大婶留个坑~~~~
			 */
			String nativeSql = "SELECT number FROM seckill WHERE seckill_id=?";
			Object object =  dynamicQuery.nativeQueryObject(nativeSql, new Object[]{seckillId});
			Long number =  ((Number) object).longValue();
			if(number>0){
				nativeSql = "UPDATE seckill  SET number=number-1 WHERE seckill_id=?";
				dynamicQuery.nativeExecuteUpdate(nativeSql, new Object[]{seckillId});
				SuccessKilled killed = new SuccessKilled();
				killed.setSeckillId(seckillId);
				killed.setUserId(userId);
				killed.setState(Short.parseShort(number+""));
				killed.setCreateTime(new Timestamp(System.currentTimeMillis()));
				dynamicQuery.save(killed);
			}else{
				return Result.error(SeckillStatEnum.END);
			}
		} catch (Exception e) {
			throw new RrException("异常了个乖乖");
         }finally {
			lock.unlock();
		}
		return Result.ok(SeckillStatEnum.SUCCESS);
	}

可以改成

public Result  startSeckilLock(long seckillId, long userId) {
		try {
			/**
			 * 1)这里、不清楚为啥、总是会被超卖101、难道锁不起作用、lock是同一个对象
			 * 2)来自热心网友 zoain 的细心测试思考、然后自己总结了一下,事物未提交之前,锁已经释放(事物提交是在整个方法执行完),导致另一个事物读取到了这个事物未提交的数据,也就是传说中的脏读。建议锁上移
			 * 3)给自己留个坑思考:为什么分布式锁(zk和redis)没有问题?(事实是有问题的,由于redis释放锁需要远程通信,不那么明显而已)
			 * 4)2018年12月35日,更正一下,之前的解释(脏读)可能给大家一些误导,数据库默认的事务隔离级别为 可重复读(repeatable-read),也就不可能出现脏读
			 * 哪个这个级别是只能是幻读了?分析一下:幻读侧重于新增或删除,这里显然不是,那这里到底是什么,给各位大婶留个坑~~~~
			 */
			String nativeSql = "SELECT number FROM seckill WHERE seckill_id=?";
			Object object =  dynamicQuery.nativeQueryObject(nativeSql, new Object[]{seckillId});
			Long number =  ((Number) object).longValue();
			if(number>0){
				nativeSql = "UPDATE seckill  SET number=number-1 WHERE seckill_id=?";
				dynamicQuery.nativeExecuteUpdate(nativeSql, new Object[]{seckillId});
				SuccessKilled killed = new SuccessKilled();
				killed.setSeckillId(seckillId);
				killed.setUserId(userId);
				killed.setState(Short.parseShort(number+""));
				killed.setCreateTime(new Timestamp(System.currentTimeMillis()));
				dynamicQuery.save(killed);
			}else{
				return Result.error(SeckillStatEnum.END);
			}
		} catch (Exception e) {
			throw new RrException("异常了个乖乖");
         }finally {
			
		}
		return Result.ok(SeckillStatEnum.SUCCESS);
	}
public Result SeckilLock(long seckillId, long userId){
		 Result result=Result.error("执行失败");
		 lock.lock();
		 result= startSeckilLock(seckillId,userId);
		 lock.unlock();
		 return  result;
	}

将锁至于事务外,这样就不会出现锁失效的问题了

评论 (0)

NL 创建了任务

登录 后才可以发表评论

状态
负责人
里程碑
Pull Requests
关联的 Pull Requests 被合并后可能会关闭此 issue
分支
开始日期   -   截止日期
-
置顶选项
优先级
参与者(1)
Java
1
https://gitee.com/52itstyle/spring-boot-seckill.git
git@gitee.com:52itstyle/spring-boot-seckill.git
52itstyle
spring-boot-seckill
spring-boot-seckill

搜索帮助