背景:

  本人上次做申领campaign的PHP后台时,因为项目上线后某些时段同时申领的人过多,导致一些专柜的存货为负数(<0),还好并发量不是特别大,只存在于小部分专柜而且一般都是-1的状况,没有造成特别特别严重的后果,但还是要反思了自己的过错。

  这次又有新的申领campaign,我翻看了上次的代码逻辑:

正文:

【先select后update】

  1. beginTranse(开启事务)
  2. try{
  3.     $result = $dbca->query('select amount from s_store where postID = 12345');
  4.     if(result->amount > 0){
  5.         $dbca->query('update s_store set amount = amount - 1 where postID = 12345');
  6.     }
  7. }catch($e Exception){
  8.     rollBack(回滚)
  9. }
  10. commit(提交事务)

  以上代码就是我第一次的写法,看似问题不大,其实隐藏着巨大的漏洞。数据库的访问其实就是对磁盘文件的访问,数据库中的表其实就是保存在磁盘上的一个个文件,甚至一个文件包含了多张表。例如由于高并发,当前有三个用户a、b、c三个用户进入到了这个事务中,这个时候会产生一个共享锁,所以在select的时候,这三个用户查到的库存数量都是>=0的。

  然后是update,假如这三个用户同时到达update这里,这个时候update更新语句会把并发串行化,也就是给同时到达这里的是三个用户排个序,一个一个执行,并生成排他锁,在当前这个update语句commit之前,其他用户等待执行,commit后,生成新的版本;这样执行完后,库存肯定为负数了。但是根据以上描述,我们修改一下代码就不会出现超买现象了,代码如下:

【先update后select】

  1. beginTranse(开启事务)
  2. try{
  3.     $dbca->query('update s_store set amount = amount - 1 where postID = 12345');
  4.     $result = $dbca->query('select amount from s_store where postID = 12345');
  5.     if(result->amount < 0){
  6.        throw new Exception('库存不足');
  7.     }
  8. }catch($e Exception){
  9.     rollBack(回滚)
  10. }
  11. commit(提交事务)

  另外,更简洁的方法:

【update & select合并】

  1. beginTranse(开启事务)
  2. try{
  3.     $dbca->query('update s_store set amount = amount - 1 where amount>=1 and postID = 12345');
  4. }catch($e Exception){
  5.     rollBack(回滚)
  6. }
  7. commit(提交事务)

========================================补充=============================================

1、这个肯定不能直接操作数据库的,会挂的。直接读库写库对数据库压力太大,要用缓存。

  把你要卖出的商品比如10个商品放到缓存中;然后在memcache里设置一个计数器来记录请求数,这个请求书你可以以你要秒杀卖出的商品数为基数,比如你想卖出10个商品,只允许100个请求进来。那当计数器达到100的时候,后面进来的就显示秒杀结束,这样可以减轻你的服务器的压力。然后根据这100个请求,先付款的先得后付款的提示商品以秒杀完。

2、首先,多用户并发修改同一条记录时,肯定是后提交的用户将覆盖掉前者提交的结果了。这个直接可以使用加锁机制去解决,乐观锁或者悲观锁。

  悲观锁(Pessimistic Lock), 顾名思义,就是很悲观,每次去拿数据的时候都认为别人会修改,所以每次在拿数据的时候都会上锁,这样别人想拿这个数据就会block直到它拿到锁。传统的关系型数据库里边就用到了很多这种锁机制,比如行锁,表锁等,读锁,写锁等,都是在做操作之前先上锁。

  乐观锁(Optimistic Lock), 顾名思义,就是很乐观,每次去拿数据的时候都认为别人不会修改,所以不会上锁,但是在更新的时候会判断一下在此期间别人有没有去更新这个数据,可以使用版本号等机制。乐观锁适用于多读的应用类型,这样可以提高吞吐量,像数据库如果提供类似于write_condition机制的其实都是提供的乐观锁。

  两种锁各有优缺点,不能单纯的定义哪个好于哪个。乐观锁比较适合数据修改比较少,读取比较频繁的场景,即使出现了少量的冲突,这样也省去了大量的锁的开销,故而提高了系统的吞吐量。但是如果经常发生冲突(写数据比较多的情况下),上层应用不不断的retry,这样反而降低了性能,对于这种情况使用悲观锁就更合适。

3、不建议在数据库层面加锁,建议通过服务端的内存锁(锁主键)

  当某个用户要修改某个id的数据时,把要修改的id存入memcache,若其他用户触发修改此id的数据时,读到memcache有这个id的值时,就阻止那个用户修改。

=======================================补充==============================================

参考资料:

mysql处理高并发,防止库存超卖】http://blog.csdn.net/caomiao2006/article/details/38568825

Mysql在高并发情况下,防止库存超卖而小于0的解决方案的更多相关文章

  1. Linux的虚拟内存管理-如何分配和释放内存,以提高服务器在高并发情况下的性能,从而降低了系统的负载

    Linux的虚拟内存管理有几个关键概念: Linux 虚拟地址空间如何分布?malloc和free是如何分配和释放内存?如何查看堆内内存的碎片情况?既然堆内内存brk和sbrk不能直接释放,为什么不全 ...

  2. 高并发场景系列(一) 利用redis实现分布式事务锁,解决高并发环境下减库存

    原文:http://blog.csdn.net/heyewu4107/article/details/71009712 高并发场景系列(一) 利用redis实现分布式事务锁,解决高并发环境下减库存 问 ...

  3. 高并发情况下分布式全局ID

    1.高并发情况下,生成分布式全局id策略2.利用全球唯一UUID生成订单号优缺点3.基于数据库自增或者序列生成订单号4.数据库集群如何考虑数据库自增唯一性5.基于Redis生成生成全局id策略6.Tw ...

  4. 小D课堂 - 新版本微服务springcloud+Docker教程_6-05 高级篇幅之高并发情况下

    笔记 5.高级篇幅之高并发情况下接口限流特技         简介:谷歌guava框架介绍,网关限流使用 1.nginx层限流 2.网关层限流 开始 mysql最大的连接数就是3千多.如果想把应用搞好 ...

  5. 关于WCF服务在高并发情况下报目标积极拒绝的异常处理

    最近弄了个wcf的监控服务,偶尔监控到目标服务会报一个目标积极拒绝的错误.一开始以为服务停止了,上服务器检查目标服务好好的活着.于是开始查原因. 一般来说目标积极拒绝(TCP 10061)的异常主要是 ...

  6. WCF服务在高并发情况下报目标积极拒绝的异常处理 z

    http://www.cnblogs.com/kklldog/p/5037006.html wcf的监控服务,偶尔监控到目标服务会报一个目标积极拒绝的错误.一开始以为服务停止了,上服务器检查目标服务好 ...

  7. Jackson高并发情况下,产生阻塞

    情况:在高并发情况下,查看线程栈信息,有大量的线程BLOCKED. 从线程栈得知,线程栈中出现了阻塞,锁在了com.fasterxml.jackson.databind.ser.SerializerC ...

  8. c# redis 利用锁(StackExchange.Redis LockTake)来保证数据在高并发情况下的正确性

    之前有写过一篇介绍c#操作redis的文章 http://www.cnblogs.com/axel10/p/8459434.html ,这篇文章中的案例使用了StringIncrement来实现了高并 ...

  9. Mysql高并发情况下的解决方案(转)

    查询了下Mysql 关于高并发的处理的资料,在这记录一下. 高并发大多的瓶颈在后台数据逻辑处理,在存储,mysql的正常的优化方案如下: 1.代码中sql语句优化 2.数据库字段优化,索引优化 3.加 ...

随机推荐

  1. alert的换行问题

    一种比较复杂的方法,但这种方法使用起来对所有型号的浏览器都能任意分辨: //浏览器类型判定 function getOs() { if(navigator.userAgent.indexOf(&quo ...

  2. 百度BAE JAVA环境项目部署和调试

    起初在一个应用挂在虚拟主机上,昨天早上虚拟主机挂了.本来考虑迁移到SAE上的,但之前发现SAE的JVM云豆消耗的太快(PS:我是中级开发者,每月 10000云豆,如果有哪位大神对SAE JAVA云豆能 ...

  3. LIstView 滚动 异步 加载更多 mono for android ScrollStateChanged ScrollState.Idle; Fling;TouchScroll

    今天项目需要实现一下列表的分页加载 找到了Listview的ScrollStateChanged方法. 和大家分享一下 //先找到Listview ListView order = FindViewB ...

  4. Snmp协议应用-扫描局域网内打印机

    .h2cls { background: #6fa833 none repeat scroll 0 0 !important; color: #fff; font-family: "微软雅黑 ...

  5. Java虚拟机10:类加载器

    类与类加载器 虚拟机设计团队把类加载阶段张的"通过一个类的全限定名来获取此类的二进制字节流"这个动作放到Java虚拟机外部去实现,以便让应用程序自己决定如何去获取所需要的类.实现这 ...

  6. 讲讲HashCode的作用

    前言 Object提供给我们了一个Native的方法“public native int hashCode();”,本文讲讲Hash是什么以及HashCode的作用 Hash 先用一张图看下什么是Ha ...

  7. 设计模式之美:Null Object(空对象)

    索引 意图 结构 参与者 适用性 效果 相关模式 实现 实现方式(一):Null Object 的示例实现. 意图 通过对缺失对象的封装,以提供默认无任何行为的对象替代品. Encapsulate t ...

  8. PPT嵌入字体的方法

    使用ppt的时候,很多时候会使用一些特殊字体,在其他计算机上无法正常显示.这个时候就需要导出PPT的时候进行字体嵌入. 1.1 常规方法 所谓常规方法,是指那些字体的许可协议允许随意分发,我们才能导出 ...

  9. hadoop学习笔记:zookeeper学习(上)

    在前面的文章里我多次提到zookeeper对于分布式系统开发的重要性,因此对zookeeper的学习是非常必要的.本篇博文主要是讲解zookeeper的安装和zookeeper的一些基本的应用,同时我 ...

  10. 使用Notepad++开发C#,一个复杂点的csscript脚本

    使用Notepad++开发C#,一个复杂点的csscript脚本: //css_dir ..\..\lib; //css_ref Geb.Image.dll; //css_ref Geb.Image. ...