• 【转】记录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改成这样

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

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

    摘自:http://h5b.net/php-mysql-high-concurrency-accident

  • 相关阅读:
    C语言之回调函数&模块化
    680. 验证回文字符串 Ⅱ
    C++指针数组和数组指针
    345. 反转字符串中的元音字母
    633.平方数之和
    IDM使用介绍篇
    路由器无线桥接WDS
    约数的个数
    密码翻译
    查找学生信息
  • 原文地址:https://www.cnblogs.com/52php/p/5669975.html
Copyright © 2020-2023  润新知