流水号生成是一个看似简单,实际上确实牵涉到复杂的数据库和业务逻辑实现的一个问题。我们先来看一般流水号的基本要求:
(1)、它首先要是顺序增长的;
(2)、它必需是数据库中每条记录唯一的;
(3)、它不能断号;
(4)、从流水号能直接看到业务发生的笔数。
好了,就这么四点基本要求,我们现在将其拿回到具体的实践环境中,看一下具体的业务实现。
(1)、所有业务终端共享流水号序列,先占号,后生成业务记录的情况:
此类设计在相应的模块打开时,用户就能看到业务单据的流水号,但却存在下面的并发问题:
两个账号可能同时请求新的流水号,我们习惯上将流水号的末次最大值加1返回,然后再将这个值保存成末次最大值,以便下次生成。但是,这就要求我们设置事务的隔离级别为可串行化事务,以保证不会同时有人读取和更新同一条记录。而一般人不注意这一点,就会造成多个业务终端得到同一流水号的问题,最终造成数据错乱。
好了,由于我们设置了串行事务,所以多个终端之间的流水号不会重复了,但新的问题又来了。
× 某个业务终端很不幸的发生了死机、断电或者程度异常中止的问题,造成申请的流水号对应的业务数据没有保存上;
× 用户取消了这笔业务办理;
如果发生这样的情况,那么结果会怎么?很明显,由于共享流水号序列,结果再次申请时,新的流水号序列明显就会是一个新值,这样就出现了断号的问题,而从流水号想直接看到业务发生的笔数的想法也就明显与事实不符了。这样子上面的后两点要求就无法满足。
好了,这个问题看起来并不是无解,我们于是想到了下面的解决方案:
将每个账号新申请的流水号单独保存起来,如果这个流水号对应的作业正常完成,则相应的记录被删除,否则,相应的流水号下次会被重用。
但显然,这也是一个看起来很好的解决方案,但却依然存在问题:
× 假设 A、B 两个终端在结束当天业务前,分别申请了新的流水号以准备进行最后一笔业务,而且A的流水号在前,B的流水号在后,但实际办理业务时,B 终端办理业务的完成,而 A 终端办理业务的用户由于某种原因取消了业务的办理,这样子,A的流水号虽然回收了,但却最终依然出现断号的情况。
× 一个很不幸的事情,系统设计是,允许同一个账号在不同的终端登录,结果有人用同一个账号在另一台机器上登录,那么现在的解决方案保存的问题就更加严重了。不仅上面的问题存在,在引入这个解决方案前的问题都还存在。
好了,这点小瑕疵假设我们能够接受,毕竟这种极端的业务情况很少见。
(2)、保存时才生成业务流水号
这种模式下,用户在最终保存之前无法看到具体的流水号。但效果是明显的,只要是采用可串行化事务,理论上数据就不会存在断号和乱号的可能。
(3)、每个账号独占私有的流水号序列,先占号,后生成业务记录情况;
这种情况下,由于独占流水号序列,所以就不存在(1)中共享序列所存在的争用问题。但是如果允许同一账号多台终端同时登录,也会存在同样的问题,所以这种序列一般情况下,都会禁止多个账号同时登录。如果一旦检测到同一账号从其它终端登录,旧账号就要被强制注销。这样来保证流水号序列满足上述要求。但明显基本要求的第(4)项只能看账号级别的业务数量,总的业务数量就需要进行汇总才能得到结果。
这种流水号的生成和保存时的规则一般是:
√ 使用末次流水号加1为新流水号
√ 保存业务数据时将末次流水号更新为业务数据对应的流水号
好了,本文探讨的流水号规则也就这些,如果大家有什么补充,欢迎提出一起讨论。