一个潜伏了 5 年的 bug:mysql的潜规则

分享到:  QQ好友和群QQ好友和群 QQ空间QQ空间 腾讯微博腾讯微博 腾讯朋友腾讯朋友 微信微信
查看查看181 回复回复6 收藏收藏 分享淘帖 转播转播 分享分享 微信
查看: 181|回复: 6
收起左侧

一个潜伏了 5 年的 bug:mysql的潜规则

[复制链接]
i0n1c 发表于 2016-8-28 23:04:06 | 显示全部楼层 |阅读模式
快来登录
获取优质的苹果资讯内容
收藏热门的iOS等技术干货
拷贝下载Swift Demo源代码
订阅梳理好了的知识点专辑
前两天策划反馈某项活动中,理论上大概千分之一中奖率的一个奖品,连续四次被同一个玩家得到了。他之前已经找了几个程序复查过代码,都说没啥问题,但是从概率上讲,肯定是有问题的,所以希望我“为程序员正名”。经过一上午的奋战,我也算不辱使命,找出了这个潜伏了5年的bug,也了解了关于mysql的一个潜规则。

一个潜伏了 5 年的 bug

一个潜伏了 5 年的 bug:mysql的潜规则 - 敏捷大拇指 - 一个潜伏了 5 年的 bug


整个业务场景大概是这样的(下面代码仅为示意,不可直接运行):

首先,每个玩家报名的时候,向数据库中插入一行,记录下活动id(activity_id),玩家uuid(player_uuid),申请序号(apply_no)以及一些其他的玩家相关信息。

同时,在内存中维护一个整数,记录当前活动的申请总人数apply_total_num。同时使用这个整数来获得下一个插入行的apply_no。

第二,由策划填写的数据表导成数据结构表示奖励结构,即每个等级的奖励各有多少份,如下所示:

[JavaScript] 纯文本查看 复制代码
awards = {
        1: 1, 
        2: 15, 
        3: 50, 
        ...
        }


第三,当活动报名完毕时,根据当前的apply_total_num和总奖励份数sum(awards.values()),生成一个长度为总奖励份数的随机的apply_no序列。比如有10000人报名,总奖励分数是1000,那么我们有可能生成如下的一个包含1000个apply_no的序列:

[JavaScript] 纯文本查看 复制代码
lucky_apply_nos = [234, 123, 1356, 8765, 12, ...]


第四,从MySQL数据库中取出lucky_apply_nos对应的uuid:

[SQL] 纯文本查看 复制代码
lucky_uuids = SELECT player_uuid FROM world_lottery WHERE apply_no IN lucky_apply_nos;


拿到lucky_uuids之后,根据awards对应分配奖励:lucky_uuids[0]对应1等奖,lucky_uuids[1]~lucky_uuids[15]对应2等奖,依此类推。

看上去没有什么问题吧。其实问题就出现在SELECT语句这里了。在这段流程中有一个想当然的推论,就是lucky_uuids的顺序是和lucky_apply_nos中的顺序一致的。但是,其实这里踩了MySQL的一个潜规则坑:

对于没有ORDER BY子句的SELECT语句,其返回顺序是和所使用的引擎有关:

  • 对于MyISAM引擎来说,其返回顺序是其物理存储顺序;
  • 对于InnoDB引擎来说,其返回顺序是按照主键排序的。


也就是说,无论哪种情况,都不会按照IN子句里的列表lucky_apply_nos的顺序返回……

具体到我们这里的情况来说,引擎使用的是InnoDB,主键是player_uuid,也就是说返回的lucky_uuids是依据palyer_uuid排过序的。

那这样会导致什么问题呢?

如果某个玩家的uuid特别小,那么只要这个玩家进入了奖励大名单(lucky_apply_nos),那么他就一定会获得价值最高的1等奖。这样这个玩家连中四次一等奖的概率就是0.1^4,而不是0.0001^4。

这个玩法如果在奖励大名单的中奖概率和各个奖项的中奖概率相差不大的时候,bug体现的就不太明显。但是在这次活动中,策划加大了奖励大名单的数量(主要是通过增加低价值奖励的数量),这样就比较容易出现某位玩家连中几次高价值奖励的情况了。

解决方法也很简单,拿到数据库的返回结果lucky_uuids之后,再进行一次shuffle即可。




总结

MySQL对于无ORDER BY子句的SELECT的语句的返回结果有潜规则:

  • 对于MyISAM引擎来说,其返回顺序是其物理存储顺序;
  • 对于InnoDB引擎来说,其返回顺序是按照主键排序的。




都看到这里了,就把这篇资料推荐给您的好朋友吧,让他们也感受一下。

回帖是一种美德,也是对楼主发帖的尊重和支持。

*声明:敏捷大拇指是全球最大的Swift开发者社区、苹果粉丝家园、智能移动门户,所载内容仅限于传递更多最新信息,并不意味赞同其观点或证实其描述;内容仅供参考,并非绝对正确的建议。本站不对上述信息的真实性、合法性、完整性做出保证;转载请注明来源并加上本站链接,敏捷大拇指将保留所有法律权益。如有疑问或建议,邮件至marketing@swifthumb.com

*联系:微信公众平台:“swifthumb” / 腾讯微博:@swifthumb / 新浪微博:@swifthumb / 官方QQ一群:343549891(满) / 官方QQ二群:245285613 ,需要报上用户名才会被同意进群,请先注册敏捷大拇指

嗯,不错!期待更多好内容,支持一把:
支持敏捷大拇指,用支付宝支付10.24元 支持敏捷大拇指,用微信支付10.24元

评分

参与人数 1金钱 +10 收起 理由
Anewczs + 10 发帖就是光荣,分享精神可嘉!.

查看全部评分

软蛋 发表于 2016-8-28 23:30:43 | 显示全部楼层
这已经脱离了代码层了
growthhacker 发表于 2016-8-29 16:21:52 | 显示全部楼层
厉害!这个bug很诡异!
攻城狮 发表于 2016-8-29 16:43:55 | 显示全部楼层
只要刨得深,bug磨成针
转行的 发表于 2016-9-2 01:54:25 | 显示全部楼层
我说呢!
手表哥 发表于 2016-9-6 21:47:40 | 显示全部楼层
采姑娘的小蘑菇 发表于 2016-11-1 00:49:20 | 显示全部楼层
这个bug太常用了
您需要登录后才可以回帖 登录 | 立即注册

本版积分规则

做任务,领红包。
我要发帖

分享扩散

都看到这里了,就把这资料推荐给您的好朋友吧,让他们也感受一下。
您的每一位朋友访问此永久链接后,您都将获得相应的金钱积分奖励
热门推荐

合作伙伴

Swift小苹果

  • 北京治世天下科技有限公司
  • ©2014-2016 敏捷大拇指
  • 京ICP备14029482号
  • Powered by Discuz! X3.1 Licensed
  • swifthumb Wechat Code
  •   
快速回复 返回顶部 返回列表