Thinkphp5 实现悲观锁
悲观锁介绍(百科):
悲观锁,正如其名,它指的是对数据被外界(包括本系统当前的其他事务,以及来自外部系统的事务处理)修改持保守态度,因此,在整个数据处理过程中,将数据处于锁定状态。悲观锁的实现,往往依靠数据库提供的锁机制(也只有数据库层提供的锁机制才能真正保证数据访问的排他性,否则,即使在本系统中实现了加锁机制,也无法保证外部系统不会修改数据)。
使用场景举例:以MySQL InnoDB为例
商品goods表,假设商品的id为2,购买数量为1,status为1表示上架中,2表示下架。现在用户购买此商品,在不是高并发的情况下处理逻辑是:
- 查找此商品的信息;
- 检查商品库存是否大于购买数量;
- 修改商品库存和销量;
上面这种场景在高并发访问的情况下很可能会出现问题。如果商品库存是100个,高并发的情况下可能会有1000个同时访问,在到达第2步的时候,都会检测通过。这样会出现商品库存是-900个的情况。显然着不满足需求!!!
使用悲观锁在处理。
当我们在查询出goods信息后就把当前的数据锁定,直到我们修改完毕后再解锁。那么在这个过程中,因为goods被锁定了,就不会出现有第三者来对其进行修改了。
注:要使用悲观锁,我们必须关闭mysql数据库的自动提交属性,因为MySQL默认使用autocommit模式,也就是说,当你执行一个更新操作后,MySQL会立刻将结果进行提交。
1 /** 2 * thinkphp使用悲观锁。悲观锁需要配合事务一起使用 3 * 商品表。购买数量为1,先锁定该商品,不让其他操作减库存。 4 */ 5 public function mysql_lock(){ 6 $num = 1; 7 $goods_id = 2; 8 $model = \db('goods'); 9 $model->execute('set autocommit=0'); 10 $model->startTrans(); 11 //使用主键或者其他索引字段时为行级锁,否则为表级锁 12 $where['id'] = $goods_id; 13 $where['status'] = 1; 14 $goods_info = $model->lock(true)->where($where)->find(); 15 if(empty($goods_info)){ 16 return '商品不存在'; 17 } 18 $total = $goods_info['total']; //商品库存 19 $sell = $goods_info['sell']; //商品销量 20 if($total<$num){ 21 return '库存不足'; 22 } 23 $data['total'] = $total-$num; 24 $data['sell'] = $sell+$num; 25 $res = $model->where(array('id'=>$goods_id))->update($data); 26 if($res){ 27 $order_model = \db('orders'); 28 $order_data['uid'] = rand(1000,9999); 29 $order_data['status'] = 1; 30 $order_data['create_time'] = date('Y-m-d H:i:s'); 31 $order_res = $order_model->insert($order_data); 32 if($order_res){ 33 $model->commit(); 34 }else{ 35 $model->rollback(); 36 } 37 }else{ 38 $model->rollback(); 39 } 40 exit; 41 42 } 43 44 45 46 /** 47 * 不加锁的情况 48 * @return string 49 */ 50 public function mysql_unlock(){ 51 $num = 1; 52 $goods_id = 2; 53 $model = \db('goods'); 54 $model->execute('set autocommit=0'); 55 $model->startTrans(); 56 $where['id'] = $goods_id; 57 $where['status'] = 1; 58 $goods_info = $model->where($where)->find(); 59 if(empty($goods_info)){ 60 return '商品不存在'; 61 } 62 $total = $goods_info['total']; //商品库存 63 $sell = $goods_info['sell']; //商品销量 64 if($total<$num){ 65 return '库存不足'; 66 } 67 $data['total'] = $total-$num; 68 $data['sell'] = $sell+$num; 69 $res = $model->where(array('id'=>$goods_id))->update($data); 70 if($res){ 71 $order_model = \db('orders'); 72 $order_data['uid'] = rand(1000,9999); 73 $order_data['status'] = 1; 74 $order_data['create_time'] = date('Y-m-d H:i:s'); 75 $order_res = $order_model->insert($order_data); 76 if($order_res){ 77 $model->commit(); 78 }else{ 79 $model->rollback(); 80 } 81 }else{ 82 $model->rollback(); 83 } 84 exit; 85 86 }
加锁和不加锁的测试请看下篇文章【Thinkphp5 用ab压力测试工具测试高并发请求】。