关于数据库‘状态’字段设计的思考与实践

最近在作订单及支付相关的系统,在订单表的设计阶段,团队成员就‘订单状态’数据库字段设计有了一些分歧,网上也有很多关于这方面的思考和探讨,结合这些资料和项目的实际状况,拟对一些共性问题进行更深一层的思考,笔耕在此,和你们一块儿探讨。html

问题综述

这里的分歧点即有团队内部的分歧点,也有网络上常见的一些分歧点,先将存在的分歧点抛出来:sql

一、订单表的‘订单状态’字段对应的字典值应当包含哪些状态值?对于‘已评论’、‘已退货’、’已退款’这类状态是放到‘订单状态’中?仍是独立一个字段标识?数据库

二、订单表的‘订单状态’字段对应的字典值如何表示?可选项有:使用数字标识、使用多‘位’存储方式标识、使用具备明确业务含义的英文字符串标识;网络

三、订单表的‘订单状态’字段使用何种类型?可选项有:number(N)、char(N)、varchar2(N);工具

若是嫌分析过程过于啰嗦,能够直接拉到最后看结论。性能

业务分析

咱们先不去看问题,先来看看和‘订单(Order)’实体相关的业务是怎样的。下面咱们会针对可能改变订单实体状态的行为已经状态变化的可能性进行详细的分析。ui

订单业务实体相关的业务流程以下:下单(create)--> 买家付款(pay)--> 卖家发货(deliver)-->买家收货(receive)-->退货(rereturn);此外,还有退款(refund)和评论(comment),这两个行为比较特殊,其前向行为可能存在多个。编码

首先,能够改变订单业务状态【这里的状态不是指‘订单状态’(OrderState)这个数据库字段,而是指实际业务状态,咱们简记为(BizState),以和OrderState区分开】的行为有哪些?按照典型电商的业务流程,主要的行为(action)有:下单、付款、发货、收货、退款/退货、评论;每一种行为的发生,都会致使订单的业务状态BizState发生变化,好比‘下单’行为会建立订单,‘付款’行为会使订单变为‘已付款’,‘发货’行为可使订单状态变为‘已发货’,‘收货’行为会使订单状态变为‘已收货’,‘评论’行为会使订单状态变为‘已评论’。‘退款/退货’action不是全部订单都支持的,为减少复杂度,暂不考虑它们。spa

其次,细分下每种action对BizState带来的影响,会发现还能够细分为四种子状态(subState):action未开始(标记为0)、action进行中(标记为1)、action成功(标记为2)、action失败(标记为3);理论上,将全部action的全部subState进行排列获得4*4*4*4*4=1024(暂未考虑‘退货’);实际上,不少组合是没有业务意义的,是不可能存在的,好比‘未开始已付款...’(***20)这一类组合是不可能发生的,应当舍弃。用表格将上述的组合分析以下:.net

经过上表,咱们能够发现些的规律:

‘下单’、‘付款’、‘发货’、‘收货’前四种action是存在依赖关系的,亦即后一个action依赖于前一个action的完成;因此,他们的SubState组合状况就会很是少;

‘评论comment’这个action的SubState和其余状态组合会有不少种可能性;除了前面了两行是‘X’,后面是‘?’或者‘Y’,‘?’是指需求上是否容许在对应的BizState上进行评论,若是容许,则每种BizState须要多出4种可能,这样组合的可能性就会变得很大。

没有业务意义的SubState组合被舍弃。表中的标黑单元格,表示这个BizState是毫无心义的,由于‘未下单’的订单对于咱们来说是不存在的,这类组合须要舍弃;一样的,还有不少其余的组合也是不存在的,被舍弃掉,未展现在上表中,如‘已下单已付款未发货已收货’这种。

一般某个action的SubState为‘1进行中’、‘3失败’时,会被忽略,但也有例外;好比‘付款’action的‘3失败’状态,和‘付款’action的‘1进行中’状态,具体分析见后面内容。

忽略全部action的‘0未开始’SubState状态。由于这类SubState对于BizState不会带来变化。

综合下来,咱们获得上表的BizState,注意这里的Comment action未进行细化处理,若是细化处理,会发现BizState的可能性会增大不少不少。

接下来咱们就以前提出的这些问题进行逐个讨论。

问题1、订单表的‘订单状态’字段应当包含哪些状态值?

什么样的‘订单业务状态’(BizState)须要记录到系统层面的‘订单状态’(OrderState)字段呢?若是记录多了,则系统处理的复杂度会增大;记录少了,那么‘订单状态’(OrderState)字段就不能完整的表示出订单实体状态变化状况。

核心状态

经过上面的业务分析可知:大部分存在依赖关系的action(create、pay、deliver、receive),他们产生的合理的SubState组合是很是少的,并且他们之间的依赖是单向依赖,状态机的处理也很简单,所以,咱们先将这部分BizState归入到OrderState中:

  • 等待买家付款
  • 买家付款成功
  • 卖家已发货
  • 买家已收货

目前的订单状态流转:

‘action行为’失败的状况

对于action的SubState是‘3失败’的处理,须要针对不一样的action进行分析。相似‘下单Create’这样的action,若是失败,则能够直接将OrderState置为‘订单建立失败’,由于Create action是第一个action,它的失败意味着Order实体出生即死,BizState置为终态,对于这个BizState应当归入到OrderState中记录,不过这个OrderState其实对于用户并没有多大用处,由于用户并不会关心下单失败的订单,他更关心的是从新下单;

对于‘支付’失败,则要看需求,若是需求要求用户能够继续支付,则订单须要保留,而且状态仍然为‘等待买家付款’,若是不容许再支付,则理论上能够将BizState置为‘支付失败’终态,因此,‘支付失败’的BizState终态也应当记录到OrderState字段中。

对于‘发货’失败、‘收货’失败的状况,一般是不会发生的,即便发生也不属于系统可以控制的范畴,系统记录并没有意义,更具建设性的作法是经过线下手段尽快解决问题,从新发货等等,因此对于这些状态系统的OrderState字段不予记录。

这样下来咱们的OrderState字典值增长到6个,加粗项为新增:

  • 建立订单失败(终态)
  • 等待买家付款
  • 买家付款失败(终态,依赖需求而定)
  • 买家付款成功
  • 卖家已发货
  • 买家已收货

目前的订单状态流转:

‘action行为’进行中的状况

对于action的SubState是‘1进行中’的处理,一样须要具体场景具体分析。‘付款’行为是用户发起的,可是并非和订单系统之间的交互,涉及到支付系统的处理,这个领域也不是订单系统可控的,但关系到钱,用户比较关系,因此对于这样一个中间态,咱们须要记录,以便用户经过订单系统查询订单状态,为便于用户理解,将此状态在OrderState中记为‘付款确认中’;‘发货’‘收货’进行中的状况,不是订单系统能够控制的领域,咱们能够把他们当着行为‘未开始’处理,好比‘发货进行中’,订单系统的OrderState值为‘买家已付款’,但给用户看到的提示信息是‘买家已付款,等待卖家发货’,实际上这时候卖家可能正在发货中,可是用户不会去关心到底有没有打包好货物什么的,因此这类‘进行中’状态能够舍弃。这样下来订单系统的OrderState字段又多了一个字典值:‘付款确认中’:

  • 建立订单失败(终态)
  • 等待买家付款
  • 付款确认中
  • 买家付款失败(终态,依赖需求而定)
  • 买家付款成功
  • 卖家已发货
  • 买家已收货

目前的订单状态流转:

‘action行为’未开始的状况

忽略全部action的‘0未开始’SubState状态。由于这类SubState对于BizState不会带来变化。

‘评论comment’的处理

最后,再来看看‘评论comment’这个action。若是需求上要求:只有买家收货后才能发起‘评论’操做,则能够任务‘评论comment’单向依赖于‘receive收货’行为,那么能够将这个action的subState对应的少许BizState(应当只有‘买家已评论’、‘卖家已评论’状态)归入OrderState字段统一记录;可是若是需求是:买家在下单后就能够开始评论,好比若是卖家发货慢了,买家能够上去吐槽,那么‘评论comment’就不是单向依赖于‘receive收货’行为了,而是多向依赖于‘pay付款’、‘deliver发货’、‘receive收货’,那么这些actions的subState组合可能性就暴增,BizState的字典取值也会暴增,显然,不该当将这么多的BizState交给OrderState来记录,而应当由一个独立的数据库字段负责记录‘评论comment’的SubState,咱们能够将这个字段取名为‘CommentState’(评论状态),它的字典值很少,只有:‘未评论’、‘买家已评论’、‘卖家已评论’;其实,对于前一种需求,也能够不讲‘评论comment’对应的SubState产生的BizState归入OrderState,由于用户对于评论与否其实并非那么关心的,也就是说‘评论comment’并非核心业务流程,为了下降核心业务流程的系统处理复杂度,将其从核心业务流程中剥离出来较好。

综上,咱们应当将‘评论comment’对应的BizState独立到一个字段中记录。

‘退货rereturn’的处理

再来看看‘退货rereturn’行为对应的BizState的处理。‘退货rereturn’并非全部订单都会经历的,可是一旦涉及,则‘退货return’在业务流程上一定是单向依赖于‘receive收货’,因此应当将‘退货return’产生的BizState(‘退货中’、‘退货成功’,‘退款失败’和‘未退货’被忽略,见上面解释)归入OrderState一并记录;这样咱们的OrderState有多了两种字典值,这里咱们不考虑一个订单中有多种商品的状况,故把‘退货成功'当着终态处理,若是是一个订单多种货物的状况,须要从新仔细分析。加粗项为新增:

  • 建立订单失败(终态)
  • 等待买家付款
  • 付款确认中
  • 买家付款失败(终态,依赖需求而定)
  • 买家付款成功
  • 卖家已发货
  • 买家已收货
  • 退货中
  • 退货成功(终态)

目前的订单状态流转:

‘退款refund’的处理

最后来看下‘退款refund’行为对应的BizState的处理。首先,咱们须要知道‘退货’和‘退款’是两种不一样的业务行为,他们的关系是:一般意义上,‘退货’必然致使‘退款’,可是‘退款’能够没有‘退货’的参与(这里不讨论特殊状况,好比对于虚拟货物来说,付款成功一般觉得着收货成功,这时候就只能是在由‘退货’致使‘退款’),好比电商容许用户付款成功后收到货物前发起‘退款’。也就是说‘退款refund’并不单向依赖于‘退货rereturn’,和‘评论comment’同样是多项依赖,因此,咱们能够参考‘评论comment’的处理方式,单独创建一个字段‘RefundState退款状态’记录‘退款refund’产生的BizState,这个状态字段的字典值有:退款中,退款成功。

其余状况考虑

另外,可能还有一些加强型需求,让客户体验更好,好比用户能够建立订单以后付款以前,将订单取消,或者由系统跑批将用户长时间未支付的订单关闭,这会产生一种新的action——‘close关闭’,对应的会产生一种新的有意义的BizState——‘订单关闭/取消’,这个不属于核心流程中的,且并没有纠结之处,不予详细讨论,罗列以下:

  • 建立订单失败(终态)
  • 等待买家付款
  • 付款确认中
  • 买家付款失败(终态,依赖需求而定)
  • 买家付款成功
  • 卖家已发货
  • 买家已收货
  • 退货中
  • 退货成功(终态)
  • 订单关闭(终态)

结论

综上,咱们能够得出放入数据库’订单状态‘字段的标准:核心业务流程,向前单向依赖。扩展到其余业务实体是同样的,这里说的’订单状态‘字段实际是指该业务实体对应的数据表的主业务状态字段。咱们把结论扩展一下:

若是某个action属于业务实体对应的核心业务流程,且该action单向依赖于其前向的action,则须要将这个action产生的BizState放入到业务实体对应的数据库表的主状态字段中记录。

OrderState字段记录的BizState业务状态有10种,其中4种是终态,其他状态为中间态。这些状态的流转关系为:

问题2、订单表的‘订单状态’字段的字典值的表示形式?

先列出可选项:使用数字标识、使用多‘位’存储方式标识、使用具备明确业务含义的英文字符串标识;对可选项作逐一解释:

a、使用数字标识——使用一个数字标识一种状态,并未要求是sequence的;如‘等待买家付款’表示为‘0’;

b、使用多‘位’存储方式标识——将某种行为是否发生对应的状态对应到一个位上,好比‘是否付款’定义在第一位,‘是否发货’定义在第二位,‘是否收货’定义在第三位,‘是否评论’定义在第四位,则状态‘卖家已收货未评论’能够表示为:0111;而‘等待买家付款’则表示为‘0000’;固然这里的‘位’多是二进制的也多是N进制,后面咱们详细讨论。

c、使用具备明确业务含义的英文字符串标识——该方案和方案a相似,不过字典值变为具备明确业务含义的英文支付串,如‘等待买家付款’表示为‘WAIT_BUYER_PAY’;

方案a是数据库字段字典的惯用方式,简单直观,可是有一个坏处在于:当字典值较多时,数据库表的使用者记不住字典的含义,须要反复查找资料确认;有人会说将字典值写到字段的注释里,这个在实践中不是很靠谱,一般表创建后,若是字段增长了字典值,一般开发人员都会忽略更改字典值;并且在使用工具(如pl/sql)查询数据库时,并不会将全部字典值展现出来;

经过问题一的分析,可知:方案b使用多‘位’存储方式会增长复杂度,并无必要,能够经过将‘是否评论’状态独立成一个字段进行表示。

方案c和方案a相似,好处在于经过字典值直接知道业务含义,坏处在于会给编码和手工查询时带来复杂度,一般人们也记不住‘等待买家付款’的英文字典是‘WAIT_BUYER_PAY’,那么手动写sql查询‘等待买家付款’时就犯迷糊了。

折中以后,咱们组合方案a和方案c,获得方案d:另外创建一张字典表,存储:数字形式的字典值、字典英文名称、字典中文简称、字典解释;订单实体表的OrderState字段使用数字做为字典值。

对于方案d,看到OrderState的数字形式状态时,能够先看看字段注释是否有此字典的定义,若是没有就取查下字典表,获得字典值和含义;在编码和手动sql查询时也会变得比较容易,数字的位数毕竟要少些;创建字典表的其余好处还有:字典的解释能够写的很详细,在报表中要求展现字典中文名时,也能直接从数据库联表查询获得,而没必要额外作一次映射。(有参考:数据库表设计(状态字段)

那么对于字典数量不多的状态字段是否有必要额外新建一张字典表呢?这个根据实际状况考虑,一般能够先不建,若是后续有业务场景须要再行建立也不迟。

而对于非业务实体表的系统日志/跑批记录表等的状态,则彻底可使用数字形式的字典,由于一般不会有业务场景使用到这些字典值,并且这些字典值域应当会比较小,因此没有必要为他们建立单独的字典表。

综上得出结论:

一、字典值域较多、变化较多、报表等业务场景会使用到的业务实体表的业务状态字段,使用‘方案d:新建字典表’的方案处理;如‘订单业务实体表’中的‘订单状态’字段。

二、字典值域较少、变化较少等业务场景不会使用到的业务实体表的业务状态字段,使用‘方案a:使用数字标识字典’的方案处理;如‘支付宝的支付流水表’的‘支付流水状态’字段。

三、系统日志/跑批记录表的状态字段,使用‘方案a:使用数字标识字典’的方案处理;如‘待收货记录表’的‘跑批状态’字段。

问题3、数据库表的‘状态’字段使用何种类型

列出可选项:number(N)、char(N)、varchar2(N),其中N是一个长度值。

这个问题主要须要考虑使用场景、扩展性、性能、存储。

‘状态’字段主要使用在查询场景,且一般是‘=’或者‘in’的查询,并无区间类的查询,故三者差异不大;

对于性能,参考[原创]在Oracle 10g,Number、Char和Varchar2类型做为主键,查询效率分析 char(N)、varchar2(N)性能优于number(N),故舍弃number(N)。

考虑到扩展性,char(N)、varchar2(N)差很少;

考虑到存储,varchar2更加占用空间更小,故选择varchar2(N)。

综上:选择varchar2(N)做为数据库‘状态’字段的类型。

问题结论汇总

一、订单表的‘订单状态’字段对应的字典值应当包含哪些状态值?对于‘已评论’、‘已退货’这类状态是放到‘订单状态’中?仍是独立一个字段标识?

若是某个action(行为,如支付)属于业务实体对应的核心业务流程,且该action单向依赖于其前向的action,则须要将这个action产生的业务状态放入到业务实体对应的数据库表的主状态字段中记录。

问题中的‘已评论’由‘评论’行为产生,而‘评论’这个action并非订单业务实体的核心业务流程,且可能存在多个前向依赖action(支付、发货、收货等),因此应当独立到一个字段标识。

问题中的‘已退货’由‘退货’行为产生,而‘退货’这个action是订单业务实体的核心业务流程,用户很是关心,且只单向依赖于‘收货’action,因此应当记录到订单业务实体表的‘订单状态’字段中。

问题中的‘已退款’由‘退款’行为产生,而‘退款’这个action是订单业务实体的核心业务流程,用户很是关心,可是这个action存在多个前向依赖action(支付、发货、收货等),因此应当独立到一个字段标识。

二、订单表的‘订单状态’字段对应的字典值如何表示?可选项有:使用数字标识、使用多‘位’存储方式标识、使用具备明确业务含义的英文字符串标识;

i、字典值域较多、变化较多、报表等业务场景会使用到的业务实体表的业务状态字段,使用‘方案d:新建字典表’的方案处理;如‘订单业务实体表’中的‘订单状态’字段。

j、字典值域较少、变化较少等业务场景使用到的业务实体表的业务状态字段,使用‘方案a:使用数字标识字典’的方案处理;如‘支付宝的支付流水表’的‘支付流水状态’字段。

k、系统日志/跑批记录表的状态字段,使用‘方案a:使用数字标识字典’的方案处理;如‘待收货记录表’的‘跑批状态’字段。

三、订单表的‘订单状态’字段使用何种类型?可选项有:number(N)、char(N)、varchar2(N);

varchar2(N)占用存储更少,且具备同等的性能、扩展性,选择varchar2(N)做为数据库‘状态’字段的类型。

参考资料

数据库表设计(状态字段)

[原创]在Oracle 10g,Number、Char和Varchar2类型做为主键,查询效率分析

相关文章
相关标签/搜索