一、悲观锁
悲观锁的方案采用的是排他读,同时只有一个进程能够操作当前的记录,事务在提交或回滚之后,锁会释放,其他的进程才能读取,在性能要求不严格的情况下,可直接使用改方案。要注意的是 SELECT … FOR UPDATE要尽可能的使用索引,以便锁定尽可能少的行数;排他锁是在事务执行结束之后才释放的,不是读取完成之后就释放,因此使用的事务应该尽可能的早些提交或回滚,以便早些释放排它锁。
//0.开始事务 begin;/begin work;/start transaction; (三者选一就可以)
//1.查询出商品信息 select status from t_goods where id=1 for update;
//2.根据商品信息生成订单 insert into t_orders (id,goods_id) values (null,1);
//3.修改商品status为2 update t_goods set status=2;
//4.提交事务 commit;/commit work;
二、乐观锁
乐观锁的方案在读取数据是并没有加排他锁,而是通过一个每次更新都会自增的version字段来解决,多个进程读取到相同num,然后都能更新成功的问题。在每个进程读取num的同时,也读取version的值,并且在更新num的同时也更新version,并在更新时加上对version的等值判断。假设有10个进程都读取到了num的值为1,version值为9,则这10个进程执行的更新语句都是UPDATE goods SET num=num-1,version=version+1 WHERE version=9,然而当其中一个进程执行成功之后,数据库中version的值就会变为10,剩余的9个进程都不会执行成功,这样保证了商品不会超发,num的值不会小于0,但这也导致了一个问题,那就是发出抢购请求较早的用户可能抢不到,反而被后来的请求抢到了。
1.查询出商品信息 select (status,status,version) from t_goods where id=#{id}
2.根据商品信息生成订单
3.修改商品status为2
update t_goods set status=2,version=version+1 where id=#{id} and version=#{version};
三、where条件下的原子操作
每次对数量进行update操作时增加where数目大于0的条件,例如num为5,只要num>0 都能执行update语句
四、redis watch方案
Redis Watch 命令用于监视一个(或多个) key ,如果在事务执行之前这个(或这些) key 被其他命令所改动,那么事务将被打断。
类似mysql的乐观锁,客多个进程读取同一条记录,但是只能有一个进程事务执行成功,后面的执行都被中断。
$num = $this->redis->get('num');
$this->redis->watch('num');//watch相应的key值,
$res = $this->redis->multi()->decr('num')->lPush('result',$num)->exec(); //执行key值的修改事务,decr($num),返回减一之后的数值
五、redis list队列方案
将商品信息保存在消息队列中,利用队列弹出操作的原子性,抢购时依次从list中pop出商品,不存在商品超卖的情况,缺点是商品多了话要把所有的商品信息存到内存上。
六、基于redis decr返回值的方式,
类似mysql的where操作。