当前位置: 首页 > 科技观察

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

时间:2023-03-14 13:35:30 科技观察

数据库中“status”字段设计的思考与实践'orderstatus'字段的字典值的表示形式是什么?5.问题3.数据库表中使用什么类型的“状态”字段?在表的设计阶段,团队成员对“订单状态”数据库字段的设计产生了一些分歧。网上也有很多关于这方面的思考和讨论。结合这些材料和项目的实际情况,拟对一些共性问题进行进一步排查。所思所想,写于此,与大家共同探讨。1.问题总结这里的分歧点包括团队内部的分歧点,以及网上常见的一些分歧点。我们先抛出现有的分歧点:1)、订单表的‘订单状态’字段对应的字典值应该包括哪些状态值?对于“已审核”、“已退货”、“已退款”等状态,是否应该放在“订单状态”中?或者它们应该由一个单独的字段标识?2)、订单表的'订单状态'字段对应的字典值如何表示?选项有:使用数字标识,使用多位存储方式标识,使用业务含义明确的英文字符串标识;3)、订单表的“订单状态”字段使用什么类型?选项有:number(N),char(N),varchar2(N);如果觉得分析过程过于啰嗦,可以直接拉到***看结论。2.业务分析先不看问题,先看一下与'Order'实体相关的业务。下面我们将对可能改变订单实体状态的行为改变状态的可能性进行详细分析。订单业务实体相关的业务流程如下:订单(create)-->买家支付(pay)-->卖家交付(deliver)-->买家接收(receive)-->退货(rereturn);此外,还有退款和评论。这两个行为比较特殊,可能有多个转发行为。首先,可以改变订单业务状态的行为有哪些【这里的状态不是指数据库字段'OrderState'(OrderState),而是实际的业务状态,我们简称为(BizState)以区别于订单状态]?按照典型电子商务公司的业务流程,主要动作包括:下单、支付、发货、收货、退款/退货、评论;每个动作的发生都会导致订单BizState的业务状态发生变化,比如'order'动作会创建一个订单,'payment'动作会把订单变成'paid','delivery'动作会将订单状态更改为'已发货','收货'动作将使订单状态更改为'已收到','审核'行为将订单状态更改为'已审核'。并非所有订单都支持“退款/退货”操作。为了降低复杂性,暂时不考虑它们。其次,细分每个动作对BizState的影响,你会发现它也可以细分为四种子状态(subState):动作未开始(标记为0)、动作进行中(标记为1)、动作成功(标记为2)、动作失败(标记为3);理论上,将所有action的所有subStates全部排列,得到4*4*4*4*4=1024('return'暂不考虑);事实上,很多组合是没有商业意义的,是不可能存在的。例如,'付款尚未开始...'(***20)的组合是不可能的,应该被丢弃。利用表格对上面的组合进行分析如下:通过上面的表格,我们可以发现一些规律:'order','payment','delivery','receipt'这前四个动作是有依赖关系的,也就是后面的动作取决于前一个动作的完成;因此,它们的SubState组合会非常少;动作“评论”的子状态和其他状态组合有多种可能性;除了前两行是“X”,然后是“?”或'Y','?'是指在需求方面是否允许对相应的BizState进行评论。如果允许的话,每个BizState还需要4种可能性,这样组合的可能性就会变得很大。没有业务意义的SubState组合将被丢弃。表中黑色标记的单元格表示这个BizState是没有意义的,因为“无序”的顺序对我们来说是不存在的,需要丢弃这种组合;同样,还有很多其他的组合如果不存在,则会被丢弃,不会在上表中显示,比如'已下单,付款未发货,已收到货'。通常,当一个action的SubState为'1inprogress'和'3failed'时,会被忽略,但也有例外;比如'payment'动作的'3failed'状态,'payment'动作状态'1inprogress',具体分析见下文内容。忽略所有操作的“0未启动”子状态。因为这种SubState不会给BizState带来变化。将它们放在一起,我们得到上表中的BizState。请注意,此处的Comment操作尚未完善。如果细化一下,你会发现BizState出现的可能性会增加很多。接下来,我们将逐一讨论之前提出的这些问题。3、问题一:订单表的“订单状态”字段应该包含哪些状态值?系统层面的“OrderState”字段需要记录什么样的“订单业务状态”(BizState)?如果记录过多,会增加系统处理的复杂度;如果记录较少,则“订单状态”(OrderState)字段不能完全代表订单实体的状态变化。核心状态从上面的业务分析可以看出:大部分有依赖关系的动作(创建、支付、交付、接收)都很少有合理的SubState组合,它们之间的依赖是单向依赖。机器的处理也很简单,所以我们先把这部分BizState包含到OrderState中:WaitingforbuyertopayBuyerhaspaidsuccessfulSellerhasshippedandbuyerhasreceived当前订单状态flow:'actionbehavior'failureSubState动作的'3failed',需要针对不同的动作进行分析。如果'OrderCreate'这样的动作失败了,可以直接将OrderState设置为'OrderCreationFailed',因为Create动作是第一个动作,它的失败意味着Order实体的诞生或消亡,BizState被设置为final对于这个BizState来说,应该是包含在OrderState记录中的,但是这个OrderState其实对用户的用处不大,因为用户不关心下单失败的订单,他更关心的是重新下单;对于“付款”失败,这取决于需求。如果需求要求用户可以继续支付,则需要保留订单,状态仍然是‘等待买家支付’。如果不允许进一步支付,理论上,BizState可以设置为“支付失败”的最终状态。因此,'PaymentFailed'的BizState最终状态也应该记录在OrderState字段中。对于“发货”和“收货”的失败,一般不会发生。就算发生了,也不在系统的控制范围之内。系统记录没有意义。更有建设性的做法是采用离线方式尽快解决问题,重新发货等,所以这些状态系统的OrderState字段不会被记录。这样我们的OrderState字典的值增加到6了,并且新增了粗体项:Failedtocreateanorder(finalstate)WaitingforbuyertopayBuyerfailedtopay(finalstate,depending)Buyer支付成功卖家发送买家收到当前订单状态流:“actionbehavior”进行中,action的SubState为“1inprogress”,这也需要具体场景具体分析。“支付”行为由用户发起,但不是与订单系统的交互。它涉及支付系统的处理。这个字段不是订单系统可控的,但是它跟钱有关,用户比较关心,所以对于这样的一个我们需要记录中间状态,方便用户通过订单系统查询订单状态。为了方便用户理解,在OrderState中将该状态记录为“支付确认”;“发货”和“收货”是进行中,不是订单在系统可以控制的地方,我们可以把它们当成'未开始',比如'发货中',订单系统的OrderState值为'该买家已付款',但给用户的提示信息是'买家已付款,正在等待卖家发货'。事实上,此时卖家可能正在发货,但用户并不关心商品是否已打包,因此可以丢弃这种'inprogress'状态。这样在订单系统的OrderState字段中增加了一个字典值:'PaymentConfirmation':Failedtocreateanorder(finalstate)WaitingforbuyertopayPaymentconfirmationBuyerfailedtopay(finalstate,dependingondemand)Buyerpayssuccessfulsellerhasshippedbuyerhasreceived当前订单状态流程:如果'actionbehavior'还没有开始,忽略'0notstarted'所有动作的SubState状态。因为这种SubState不会给BizState带来变化。'评论评论'的处理,我们看一下'评论评论'的动作。如果需求要求买家收到货后才能发起‘评论’操作,那么任务‘评论’可以单向依赖‘收货’行为,那么对应的少量BizState该动作的subState(应该只是'Buyerhascommented','Sellerhascommented'状态)被包含在OrderState字段的统一记录中;但如果需求是:买家下单后可以开始评论,比如卖家发货慢,买家可以投诉,那么评论不是单向依赖于收货的行为,但是多向依赖于'paypayment','deliverydelivery','receivereceipt',那么这些动作的subState组合可能性是BizState字典的值也会急剧增加。显然,这么多的BizStates不应该交给OrderState来记录,而是应该有一个独立的数据库字段来负责记录'comment'这个SubState。我们可以将这个字段命名为'CommentState'(评论状态),它的字典值并不多,只有:'未评论','买家已评论','卖家已评论';其实对于前一个需求,没必要再提'comment'对应的SubState生成的BizState包含在OrderState中,因为用户并不真正关心评论,也就是说'comment'不是一个核心业务流程。为了降低核心业务流程的系统处理复杂度,最好将其从核心业务流程中分离出来。综上所述,我们应该将'comment'对应的BizState独立记录到一个字段中。‘returnrereturn’的处理下面看一下‘returnrereturn’这个行为对应的BizState的处理。并不是所有的订单都会经历'returnrereturn',但是一旦涉及到'returnrereturn'在业务流程中肯定是单向依赖'receivereceipt'的,所以应该是由'returnrereturn'BizState('returning'、'退货成功'、'退款失败'、'未退货'忽略,见上面的解释)包含在OrderState中,一起记录;所以我们的OrderState多了两个字典值,这里我们不考虑一个订单有多个商品的情况,所以把“返回成功”作为最终状态。如果是一个订单中有多种商品的情况,需要再次仔细分析。新增加粗项目:创建订单失败(最终状态)等待买家付款确认买家付款失败(最终状态,视需求而定)买家付款成功卖家已发货买家已收到商品退货成功(finalstate)当前订单状态流程:'refund退款'的处理***下面看'refund退款'行为对应的BizState的处理。首先,我们要知道“退货”和“退款”是两种不同的商业行为。它们的关系是:通常意义上的“退货”必然导致“退款”,但“退款”可以没有“退货”(这里不讨论特殊情况,例如虚拟商品,支付成功通常表示成功收货,此时只能是“退货”导致的“退款”)。例如,电子商务允许用户在支付成功后收到商品前发起“退款”。也就是说'refund退款'不是单向依赖'returnreturn',而是像'commentcomment'一样是多重依赖,所以我们可以参考'commentcomment'的处理方式,单独创建一个字段'RefundStaterefund'State'记录了'refund退款'产生的BizState,这个状态字段的字典值为:Refunding,退款成功。考虑其他情况。此外,可能会有一些增强的要求,以使客户体验更好。例如,用户可以在创建订单后取消订单,然后再付款,或者系统会运行一个批处理,关闭用户长时间未付款的订单,这会引发一个新的动作——‘关闭’,对应到一个新的有意义的BizState——‘orderclose/cancel’,这个不是核心流程的一部分,也没有纠结,没有详细讨论,罗列如下:Failedtocreateorder(finalstate)WaitingforbuyertopaypaymentconfirmationBuyerfailedtopay(finalstate,depending)BuyerpaidsuccessfullySellerhasshippedbuyerhasreceivedthereturnedproductReturnedsuccessful(FinalState)OrderClosing(FinalState)结论综上所述,我们可以得出标准放入数据库“订单状态”字段:核心业务流程,前向单向依赖。扩展到其他业务实体是一样的。这里所说的‘订单状态’字段,其实是指业务实体对应的数据表中的主要业务状态字段。我们把结论延伸一下:如果一个动作属于业务实体对应的核心业务流程,并且该动作单向依赖于它的前向动作,则需要将这个动作产生的BizState放到业务实体对应的数据库表中记录在主状态字段中。OrderState字段中记录了10个BizState业务状态,其中4个为最终状态,其余为中间状态。这些状态的流转关系是:4.问题2.订单表的“订单状态”字段的字典值的表达形式是什么?先列出选项:使用数字标识,使用多位存储方式标识,使用意义明确的商务英文字符串标识;一一解释选项:a.使用数字标识——用一个数字来标识一个状态,不要求是一个序列;例如,“等待买家付款”表示为'0';b、使用多个'bit'的存储方式标识——对应某个行为是否发生的相应状态,比如第一个bit定义'是否支付',第二个bit定义'是否发货'bit,'第三位定义“是否收到”,第四位定义“是否评论”,则状态“卖家已收到货物,未评论”可表示为:0111;而“等待买家付款”表示为'0000';当然,这里的‘位’可能是二进制的,也可能是N进制的,这个我们后面会详细讨论。C。使用业务含义明确的英文字符串标识——该方案与方案a类似,但字典值变为业务含义明确的英文支付字符串,如'waitingforbuyertopay'表示为'WAIT_BUYER_PAY';方案a是数据库字段字典的常用方式,简单直观,但有一个缺点:当字典值较多时,数据库表的使用者记不住字典的意思,需要反复查找资料确认;有人会说,字典的值是写到字段中的,在注释中,这个在实践中不太靠谱。通常,在创建表后,如果该字段添加了字典值,通常开发人员会忽略更改字典值;而在使用工具(如pl/sql)查询数据库时,并不会显示所有的字典值;通过问题1的分析可以看出,方案b中使用多位存储会增加复杂度,没有必要。可以用独立的“是否评论”状态表示成一个字段。方案c与方案a类似。好处是可以直接通过字典值知道业务含义。缺点是会给编码和人工查询带来复杂性。通常人们记不住“waitingforbuyertopay”的英文词典是'WAIT_BUYER_PAY',那么在手动写sqlquery'waitingforbuyertopay'时就会一头雾水。妥协后,我们将plana和planc合并,得到pland:再创建一个字典表,存储:数字形式的字典值、字典英文名、字典中文简称、字典解释;订单实体表的OrderState字段使用数字作为字典值。对于方案d,当看到OrderState的数字状态时,可以先查看字段注解是否有本字典的定义。如果没有,查字典表,得到字典值和含义;也会用在编码和手动sql查询上,变得更容易,毕竟位数少了;建立字典表的其他好处是:可以详细写字典的解释,当需要在报表中显示字典的中文名称时,也可以直接从数据库连接表中查询,无需做一个额外的映射。(参考:数据库表设计(状态字段))是否需要为状态字段创建一个额外的字典表,字典数量很少?这是根据实际情况。通常,它不能先建造。如果以后有业务场景。再创作也不迟。对于非业务实体表的系统日志/批记录表的状态,可以使用数字字典,因为通常没有业务场景使用这些字典值,这些字典的取值范围应该比较小。所以不需要为它们创建单独的字典表。综上所述,得出结论:1)、业务实体表的业务状态字段,在业务场景中使用的业务实体表,如值域多且变化多的字典、报表等,采用'Schemed的方案处理:新建字典表';例如“订单业务实体表中的‘订单状态’字段”。2)、业务实体表的业务状态字段,字典值字段少,变化少,报表等业务场景不会用到,使用“方案a:使用数字标识字典”的方案;如“支付宝的支付流水表的‘支付状态’字段’。3)、系统日志/运行批记录表的状态字段,使用‘方案a:使用数字识别字典’的方案处理;如作为‘收货记录表’的‘流水批次状态’字段。5.问题3.数据库表的'status'字段应该使用什么类型来列出选项:number(N),char(N),varchar2(N),其中N是一个长度值。这个问题主要需要考虑使用场景、扩展性、性能和存储。'status'字段主要用在查询场景,一般是'='或者'in'的查询,没有区间类的查询,所以三者区别不大;性能方面,参考Oracle10g中[原文],使用Number、Char和Varchar2类型作为主键,查询效率分析表明char(N)和varchar2(N)性能优于number(N),所以number(N)被丢弃。考虑到可扩展性,char(N)和varchar2(N)是相似的;考虑到存储,varchar2占用的空间较小,所以选择varchar2(N)。总结一下:选择varchar2(N)作为数据库“状态”字段的类型。6、问题结论总结1)、订单表的‘订单状态’字段对应的字典值应该包含哪些状态值?对于'reviewed'和'returned'等状态,是否应该放在'orderstatus'?还是独立一个字段标识符?如果一个动作(行为,比如支付)属于业务实体对应的核心业务流程,并且该动作单向依赖于它的前向动作,那么这个动作产生的业务状态需要放到业务实体中记录相应数据库表的主状态字段。问题中的“commented”是由“comment”动作产生的,“comment”动作不是订单业务实体的核心业务流程,可能有多个前向依赖的动作(付款、发货、收货、等),所以它应该独立于一个字段标识符。问题中的“退货”是由“退货”动作产生的,“退货”动作是订单业务实体的核心业务流程。用户对它很关心,只依赖一个方向的“收货”动作,所以应该记录在订单业务实体表的‘订单状态’字段中。问题中的“refunded”是由“refund”动作产生的,“refund”动作是订单业务实体的核心业务流程,用户非常关心,但是这个动作有多个前向依赖的动作(付款、发货、收货等),所以应该在一个字段中独立标识。2)订单表的“订单状态”字段对应的字典值如何表示?选项为:使用数字标识、使用多位存储方式标识、使用业务含义明确的英文字符串标识;我。Dictionaryvalue多域、多变更、报表等业务场景中使用的业务实体表的业务状态字段,采用'Schemed:NewDictionaryTable'的方案进行处理;例如“订单业务实体表”字段中的“订单状态”。j.业务实体表的业务状态字段,字典值字段少,变化少,报表等业务场景不会用到。使用‘方案a:使用数字识别字典’的方案;比如'支付宝的支付流水表'支付状态'字段。k.系统日志/运行批记录表的status字段采用方案a:使用数字识别字典进行处理;比如‘收货记录表’的‘运行批次状态’字段。3)订单表的'订单状态'字段使用什么类型?选项有:number(N)、char(N)、varchar2(N);varchar2(N)占用存储少,性能相同,为了扩展性,选择varchar2(N)作为数据库'status'字段的类型。7.参考数据库表设计(status字段)[原创]Oracle10g中,Number、Char和Varchar2类型作为主键,查询效率分析