1、前言
在不少应用场景中,一般须要给数据加上一些标识,以代表这条数据的某个特性,如:标识用户的性别、标识订单支付的渠道、标识商品的类型等等。在数据库设计时,一般咱们会单独用一个字段来存储这些标识,如:可用gender字段来标识用户的性别,其值为“男”、“女”、“未知”这3种值中的一个;对于普通的具备有限固定的几个值的标识,这样天然没有什么问题,可是,对于一些同时具备多个属性且变化较大的就有些不合适了,比方说,标识智能手机的商品的类型时,其类型可标识为电子商品、娱乐商品、数码商品等多种类型,其具备的可分类属性众多且不固定;这些,就是本文须要讨论的:关于数据库设计时,给数据设计标识字段时的一些思考。前端
2、常见场景、问题与解决方法
场景一:
问题与分析:
用户是任何系统中最为重要的组成部分之一,在设计存储用户信息时,性别是用户信息的重要组成部分,应该如何存储呢?性别有 “男”、“女”2种状况,若是某个用户没有具体性别数据的话,能够标识为“未知”,所以,每个用户,性别有“男”、“女”、“未知”共3种状况,咱们须要在数据表字段中须要存储这3种状况的一种(实际应用可能须要更多状况以知足具体业务,这里姑且以常见的3种为例)。
思考与方法:
方法一:在用户信息表中,添加一个性别字段,其数据类型为char,其值为“未知”、“男”、“女”中的一个,当须要使用该值时(前端展现、分析统计等),直接取出使用便可。
方法二:在用户信息表中,添加一个字段,其数据类型为tinyint(一个字节),其值为 “0”、“1”、“2”,分别对应“未知”、“男”、“女”这3种状况,其中0对应的未知为默认值。当须要使用该值时,可针对不一样的使用场景进行简单的转换,如前端需展现时可由数字转换成相应含义文字后再展现。
方法一的优势是语义明确、便于前端展现,不足之处有相对占用存储空间多、数据传输更耗流量等;方法二的优势是相对占用存储空间少、数据传输更省流量、必定程度上有利于数据统计分析,不足之处有展现时须要转换、数字语义须要约定等。笔者我的推荐使用方法二。(换个角度看,须要转换也带来了必定的灵活性,如:若某个业务须要展现性别为“保密”、“帅哥”、“靓女”,这样咱们只需修改转换的地方便可,而不需修改数据库数据,这也有利于一个用户中心为不一样的具体业务线提供服务)java
场景二:
问题与分析:
电商平台一般会划分商品品类,如服饰类、食品类、数码类、书籍类等等,而有的商品可能具备多个商品品类属性,如智能手机,其既可划为数码类、又可划分为手机类、智能设备类,常见的场景是显示商品所属品类、修改商品的所属品类、查询某个品类下有哪些商品等,在这种状况下,该如何存储呢?
思考与方法:
假如平台的商品品类共有n种,那么,理论上某个商品的所属品类可能的组合就有[2^n-1]种状况,固然,实际上不会有这么多组合,但像用户选择兴趣爱好之类的场景可能的组合就多了,所以,上文场景一中的2种方法已再也不适用。
可行的一个方法是:再引入一个品类关系表,用于存储商品品类与各商品的关系。这样的话,商品信息表自己用一个字符字段直接存储其所属品类信息,以知足基本需求,而操做某个品类下的商品的相关业务,则可经过品类关系表去作;一旦发生数据的变化,则同时维护这2个地方的数据。(事实上,一般的作法是,一件商品最多只能划分为有限的商品品类,一我的最多只能选择有限个兴趣爱好,如:一件商品最多只能所属3种品类,一我的最多只能选5个兴趣爱好)数据库
场景三:
问题与分析:
在电商系统中,一般会经过各类优惠的方式来促销,如给用户发各类优惠券、积分抵扣等,用户提交订单时可使用知足条件的各类优惠;那么,如何存储该订单具体使用了哪些优惠信息呢?
思考与方法:
与以上两种状况有所不一样,以上的两种场景更多的业务场景是前端的展现与业务查询,而这种场景更可能是标识优惠以计算用户实际所需支付金额,以及为后续业绩统计、制定促销计划、提升用户活跃度等提供数据依据。
这里咱们举一个具体例子来逐步分析。
实例分析:
假设某平台为A平台,其平台当前可以使用的优惠方式有如下几种:数据库设计
序号 优惠内容 使用条件 是否长期有效 备注
1 用户帐户余额 直接抵扣现金 是 用户充值所得(平台奖励吸引的充值,如:充100送10元)
2 平台积分 100积分抵扣1元 是 经过参与平台活动、购物行为积累获取
3 平台币(A币) 直接抵扣,1个币抵扣1元 是 平台奖励(相似Q币之类的概念,姑且叫A币)
4 满减卷5元 满100减5元 否 平台活动促销发放
5 免邮费 订单总金额符合条件便可 是 平台单笔订单总金额满199元免邮费
用户下单时,只要是知足各优惠的使用条件,就能够叠加使用各类优惠,那么,数据库如何存储用户具体使用了哪些优惠呢?(实际各类促销优惠可能更多,尤为是各类优惠券,这里姑且以5种举例)
分析:
在这个业务场景中,用户下单时最多能够同时使用5种优惠抵扣方式,用户可能使用的优惠组合共有 2^5-1=31 种,在最终计算用户的订单实际须要支付的金额时,如何标识并存储用户到底使用了哪一种优惠组合呢?
若是单独用一个普通标识字段来标识存储,实现起来是比较简单,可是其须要标识的组合种类实在有点多,不太利于编码与后续扩展,试想,若是新加了一种优惠类型,其须要添加多少种组合标识啊,且呈指数式爆长,这种方式显然不太合理。若是采用另外引入一张关联表的方式,专门用一张关联表来存储订单使用的优惠组合信息,每使用一种优惠就添加一条关联记录,相比单独使用普通字段标识,这在必定程度上减小了设置标识的繁琐性,增长了灵活性(每多使用一种优惠就添加一条关联记录),可是,同时也带来了另外一些问题,其中主要问题是:新增一张关联表后,数据维护起来麻烦。在互联网场景下,数据量一般是很是大的,像订单数据通常都须要进行数据库sharding,以应对数据量暴涨后数据库的读写性能瓶颈,增长系统的水平扩展能力。所以,另外增长一张数据量是订单数据自己数据量几倍的关联表也显然不太合适。
那么,有没有一种方式既方便标识存储又方便扩展呢?
咱们试着以一种“特殊标识位”的方式来实现,具体思路以下:
a、定义一个标识位 mask 用于标识存储优惠信息;
b、mask存储的值并非直接存一、二、3之类的十进制数字,而是存储一个二进制数转化后的十进制数,这些一、二、3之类的优惠数字表示占二进制数的第几位(从右至左数);
c、具体数据的存储、读取判断经过工具类转换进行。
例:
int 数据类型4个字节,共32位,除去符号位,可用于标识的位数有31位,即最多能够标识31种优惠状况,而若是是long数据类型的话,能标识的种类就更多了。
在以上共5种优惠方式场景中,可按以下标识存储:
说明:若用户使用了优惠1,则使用二进制数 00000001 标识,若使用了优惠2,则使用二进制数 00000010 标识,存储到DB时,转换成对应十进制数分别对应一、2;若同时使用了优惠1、优惠2,则使用二进制数 00000011 标识,最终存储到DB的对应十进制数是3。其它优惠项,所占的二进制位依次类推。
代码示例以下:
DiscountEnum.java
---------------------
做者:对门山上
来源:CSDN
原文:https://blog.csdn.net/cndmss/article/details/54232738工具