关于生成订单号的解决方案
电子商务及类电子商务的系统愈来愈多,我相信订单号问题是这类系统中最多见不过的一个问题了,但今天仍是想谈谈。
这几天因为工做须要接手了另一同事前期开发的一个交易系统,本来使用的是uniqid()函数生成的。uniqid()是根据系统时间通过必定算法获得的一个结果,关于uniqid()的详情手册上很清楚。
当时的生产方式是:
$order_sn = str_replace('.', '', uniqid('', true));
这种方式理论上说会重复,可是在实际应用中我相信这种重复能够认为是不可能事件。可是,若是这件事情到此就结束的话我也就不会再写这篇文章。这几天作支付接入,国内某大型网络支付机构只支持传递最多16位的订单号,无奈我只得调整订单号的生产规则。
其实关于生成订单号的方式很是多,大体有如下几个参数被用到:一、自增字段,二、系统时间,三、随机数,四、流水号。
1、数据库自增字段
2、简单的使用系统时间
3、系统时间加随机数
4、系统时间加流水号
先说说数据库自增字段,这种方式是最简单有效的方式,但同时也存在很大的弊端:
一、以mysql为例的int类型最多存储10位的数字,若是使用bigint则在使用php的mysql_insert_id()取上次插入id时会出现错误,固然这个错误是能够采用某些方法避免的。
二、不少时候业务逻辑须要在数据未插入系统以前就得到订单号以进行一系列的处理,这样就容易出错。好比当并发较高的时候系统获取到下一次插入的ID应该是10000,但是当真正insert的时候发现10000已经被其余插入行使用。
三、很容易透露出系统的销量,从商业层面说这种方式不太合适。
四、表现不够直观,不能经过订单号简表达订单信息
简单的使用系统时间也能够有多种方式好比直接使用time()生成10位数字,这种方式基本避免了数据库自增字段的大部分弊端,但同时也产生的一些新的问题,好比:并发量稍高(峰值每秒一次以上,相信这是个很小的值)就会产生相同订单号,而这是业务逻辑所不容许的。为了解决重复订单问题而使用随机数或者流水号。
先说随机数,这东西就跟看上去的字面意思同样,总显得不那么可靠,我认为尽可能不使用它参与惟一标识。
再说流水号,既然叫流水号,它的性质其实和自增字段同样,不一样的是或许天天或者每个月流水号又会从新计数。总得有个地方来保存下一个(或者当前使用过的最大)流水号的值,若是存在文件中那就须要考虑这个文件的读写锁的问题,就这个问题估计足够写书了,在此不予讨论。若是以自增方式存在DB中,那么咱们在程序生成订单号以前须要多访问(至少)一次DB,这也就下降了程序性能,要知道数据库访问对程序的性能影响是很是明显的
上面是一大堆废话,说说个人解决思路(PHP),固然同时别忘了大前提是:限制长度16位
第一步:
$order_sn = date('ymdHis').substr(microtime(),2,4);
其实这种方式基本已经知足需求了,无需访问DB无随机数参与。可是若是两次请求在相同的十万分之一秒内产生,那么相同订单就产生了,看可否有办法继续提升。
date(‘His’)所表达的结果其实就是000000到235959,并且其中不少数字不会被用到好比126998。一天86400秒,若是从一天的0:0:0算起直到23:59:59使用00000-86400就能够彻底表示,这样下来咱们就彻底能够把date(‘His’)换成五位数字。既然time()函数就是按秒计数,那咱就取time()结果的后五位,同一天以内后五位不会重复出现,好比今天0:0:0后五位是98765,那么到今天23:59:59后五位就应该是98765+86400去掉最高位,相信这个应该是很好理解的。
这就产生了第二步的结果:
$order_sn = date('ymd').substr(time(),-5).substr(microtime(),2,5);
这样一来也致使没法直观的表达出订单生成的时分秒,但我认为(或者说从业务角度理解)这个属于可接受范围。同时这样处理出现重复订单的几率就下降到了第一步的1/10,我觉得这应该不算一个小数字。还不满意?OK,那继续!
想要继续下降重复可能性那就继续提升时间精度,可是咱们的长度限制只有16位,看来只有减小部分不长变更的字符。
date(‘ymd’)产生6位字符,而前两位在一年以内都不会变化,第三到第四位也就是01-12。
前两位咱们可使用A-Z的字母来表示,系统开始运行的那一年用A,第二年用B,第三年用C……类推,我相信我写的这程序运行不了10年。第三第四位彻底可使用一位16进制数表示,这样咱就又节约了两位字符,这就能够在末尾加上00-99的随机数。
如今看第三步的结果
$year_code = array('A','B','C','D','E','F','G','H','I','J');
$order_sn = $year_code[intval(date('Y'))-2010].
strtoupper(dechex(date('m'))).date('d').
substr(time(),-5).substr(microtime(),2,5).sprintf('d',rand(0,99));
理论上说出现重复订单号的几率又降到了第二步的1/100。
作个简单测试,写个php文件,连续10次echo出这三步结果获得的$order_sn,中间无任何多余程序。
第一种方案基本获得10个相同的结果。
第二种方案基本获得10个不一样的结果,主要是后两位不一样,通常末位差一。
第三种方案获得10个不一样结果末四位不一样
固然这个测试不具有多少说服力
优势:
一、不用操做数据库,性能较高。
二、较为直观,不难看出订单产生的大体时间
三、订单号重复的几率极小,只有程序在百万分之一秒内同时处理一个以上的生成订单号请求,并且同时生成的0-99的随机数也同样才会出现重复的订单号。