zoukankan      html  css  js  c++  java
  • 【转】记录PHP、MySQL在高并发场景下产生的一次事故

    看了一篇网友日志,感觉工作中值得借鉴,原文如下:

    事故描述

    在一次项目中,上线了一新功能之后,陆陆续续的有客服向我们反应,有用户的个别道具数量高达42亿,但是当时一直没有到证据表示这是,确实存在,并且直觉告诉我们,这是不可能的,就一直没有在意,直到后来真的发现了一个用户确实是42亿,当时我们整个公司都震惊了,如果有大量用户是这样的情况,公司要亏损几十万,我们的老大告诉我们,肯定是什么地方数据溢出的,最后我们一帮人,疯了似的查代码,发现……

    如果按照正常的程序逻辑走下去,代码是完全没问题,但是我发现了一个地方,如果在高并发的情况下,是会走出错误的程序逻辑的,为什么我看到了,很简单,我在上一家公司,因为这个问题困扰了我一个多月,真是刻骨铭心呀!不多说了,说多了都是泪呀!上代码,重现场景(以下都是一些简单的代码,用来重现场景),道具名就定义为props。

    <?php
    
    mysql_connect("localhost", "mysql_user", "mysql_password");
    
    mysql_select_db("user");
    
    $consume_props_count = 10; // 要消耗的道具数量
    
    $select = 'SELECT `count` from `user` where `userid`=123456';
    
    $query = mysql_query($select);
    
    $row = mysql_fetch_assoc($query);
    
     
    // 对比当前道具数量
    
    if ($row['count'] < $consume_props_count) {
    
        return false;
    
    }
    // 扣除道具数量
    $update = "update `user` set `count`=`count`-{$consume_props_count}";
    $query = mysql_query($update);
    return mysql_num_rows($query);
    ?>

    大家可以看到,如果按照正常的扣除道具的流程来走,这个是没有问题的,但是在高并发场景下,两次扣道具的查询极有可能获取的是同一个结果,然后他们都能通过对比当前道具数量这一步逻辑,但是加入第一次的扣道具的操作把道具扣光了,或者扣的不够第二次继续扣了,想想会发生什么样的情况!

    坑爹了!第二次会把道具数量扣为负数。

    但是这依然不能解释这42亿的溢出那里来的!哎,祸不单行的古语再次应验了,我们的count字段的数据类型是Unsigned int,坑爹的MySQL,假如字段是Unsigned int,然后输入了一个负数,它就会让这个数字变为42亿这个巨大的数值。

    看明白了吧!这42亿就是这么来的,坑啊!

    解决方案的话,可以在update的sql改成这样

    1 <?php
    2 $update "UPDATE `user` SET `count`=(CASE WHEN `count`<={$consume_props_count} THEN 0 ELSE `count`-{$consume_props_count} END) WHERE `userid`=123456"
    3 ?>

    这样,就不会在扣成负数了,另外以防万一,还要将Unsigned int的字段类型,改为int

  • 相关阅读:
    Linq to OBJECT延时标准查询操作符
    LINQ to XML
    动态Linq(结合反射)
    HDU 1242 dFS 找目标最短路
    HDu1241 DFS搜索
    hdu 1224 最长路
    BOJ 2773 第K个与m互质的数
    ZOJ 2562 反素数
    2016 ccpc 杭州赛区的总结
    bfs UESTC 381 Knight and Rook
  • 原文地址:https://www.cnblogs.com/qxbj/p/4310297.html
Copyright © 2011-2022 走看看