MySQL数据orderby时间排序bug

懒得看我废话的同学直接看结论:
MySQL数据库中datetimedatetime(n) 0<=n<=6 是有精度区别的,如果你默认datetime,那其实只是精确到了秒,如果此时基于这个字段排序,同一秒的请求排序很有可能不是你期望的结果。下面开始介绍这次踩坑的经历:

最近为公司做了一个简单的用户账户项目中台,其中涉及了用户积分,转账,提现等,由于第一次做缺乏经验,再加上一旦涉及钱的项目肯定是要求正确率极高,所以踩了不少坑,这里记录其中一个关于MySQL数据排序的坑。

账户项目很重要的一点是要记清楚账户流水,这里我们的设计是将流水表和用户账户表合二为一,因为本身项目是基于一个类似于邀请好友得金币换现金的活动做的,一个用户存在待解冻,可用,体现中,已使用四种金币类型,这里我们就设计成了一个用户存在四种账户类型,设计一张流水表记录四种账户类型的每一笔流水,当新流水来的时候一般是通过userId和账户类型去查询数据,只查最新的一条,数据中的总额就是用户当前账户类型的最新数额,看似很简单的一个东西实际做起来却是特别多的坑。

账户首要考虑的就是数据的准确和安全,这就考验了并发和锁的设计,这个也踩了不少坑,后续打算单开一篇文章讲;

这次踩的另外一个坑是我上面提到的“最新一条记录”这个概念,当时在写代码的时候也没多想,随手写了order by create_time desc,直到线上出了诡异的bug才明白这么写的危害。上线不久之后发现有人利用我们的漏洞薅羊毛,刷了大量金币,我们技术团队也是第一时间修复了漏洞,但是我在整理数据的时候发现了一个现象,有些薅羊毛用户的金币数似乎少了很多
例如:原本用户有100金币,假如邀请一个好友给10金币,我发现在有的时候会有连续两条流水,明明是两次邀请,但是加完10金币后的总金额都是110,看到这里肯定第一反应就是锁和并发出问题了呗,出现了线程不安全的情况导致同时读取了旧数据,但是仔细检查了代码和测试之后发现应该不会出现线程安全的问题,然后就开始陷入了翻日志和测试的代码的死循环中,几乎浪费了一下午时间,其实原因很简单,就是在选择所谓的“最新一条记录”时使用了order by create_time,而create_time在表中是一个dateTime,也许你会奇怪,诶我也用过时间比较啊,没问题啊。

注意,我设计的dateTime只是dateTime,而你们数据库的表中dateTime正确做法应该是dateTime(n) 0<=n<=6,这样规定了精确度之后才能真正通过时间比较,像我这种没有设置精确度其实默认是到秒,这个你可以通过如下sql自己看一下结果:

select now();
select now(3);
select now(6);
select now(7); -- 报错

由于我们的单位是一秒,虽然很短,但是对于并发系统来说已经是很长很长的时间了,这也就是为什么薅羊毛的用户出了问题,因为他们大多是利用接口漏洞疯狂刷金币,导致经常出现一秒内多条的情况,所以order by自然容易丢失数据,幸好及时发现而且大部分影响了非正常用户,没有造成更大的损失。

这里也确实提醒了我,在使用数据库字段的时候,不管是当作条件还是分组还是排序,都应该尽量使用稳妥或者有明确业务意义的字段,例如排序完全可以使用主键id排序,天然的自增字段,并且具有唯一性。

果然啊,知识学的再多,没有经验把他们串起来,还是差得远啊。

  • 3
    点赞
  • 4
    收藏
    觉得还不错? 一键收藏
  • 2
    评论
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值