1. 商品规格数据结构

商品中都有属性,不同商品,属性往往不同。

1.1 规格属性内容

在生活中,我们很容易发现,虽然商品不同,规格不同。但是统一分类的商品,比如都是手机,其规格是一致的,但是值不一样。也就是说,商品的规格参数应该是与分类绑定的。每一个分类都有统一的规格参数模版,但不同商品其参数值可能不同。
因此:

  • 规格参数的名称(key)与值(value)应该分开保存
  • 一个分类,对应一套规格参数模版,只有规格参数key,没有值;
  • 一个分类对应多个商品,每个商品但规格值不同,每个商品对应一套规格值。

1.2 横表与竖表

值我们暂且不管,新增商品时,再来填写规格参数值即可。我们现思考规格参数模版(key)该如何设计。

  • 规格数据首先要分组,组内有不同的规格参数
  • 不同分类,其分组名称也不同
  • 不同分组,组内属性也不同

这样就意味着:有多少分类,就有多少分组。
如果按照传统设计,我们会以规格参数作为数据库字段名。如品牌、型号都是字段,那么表的字段就会无限多。这样的表称为横表
一条信息,描述所有数据。例如:

我们不这样做,我们一条信息,只描述一条规格属性,也就是把规格参数作为字段的值,而非字段本身。这样的设计称为竖表设计。
例如:

不过,规格和规格组也要单独保存,都采用竖表设计。所以我们有两张表:

如图:

1.3 SpecGroup表结构

规格参数分组表:tb_spec_group

CREATE TABLE `tb_spec_group` (
  `id` bigint(20) NOT NULL AUTO_INCREMENT COMMENT '主键',
  `cid` bigint(20) NOT NULL COMMENT '商品分类id,一个分类下有多个规格组',
  `name` varchar(50) NOT NULL COMMENT '规格组的名称',
  PRIMARY KEY (`id`),
  KEY `key_category` (`cid`)
) ENGINE=InnoDB AUTO_INCREMENT=14 DEFAULT CHARSET=utf8 COMMENT='规格参数的分组表,每个商品分类下有多个规格参数组';

1.3.2 SpecParam规格参数

规格参数表:tb_spec_param

CREATE TABLE `tb_spec_param` (
  `id` bigint(20) NOT NULL AUTO_INCREMENT COMMENT '主键',
  `cid` bigint(20) NOT NULL COMMENT '商品分类id',
  `group_id` bigint(20) NOT NULL,
  `name` varchar(255) NOT NULL COMMENT '参数名',
  `numeric` tinyint(1) NOT NULL COMMENT '是否是数字类型参数,true或false',
  `unit` varchar(255) DEFAULT '' COMMENT '数字类型参数的单位,非数字类型可以为空',
  `generic` tinyint(1) NOT NULL COMMENT '是否是sku通用属性,true或false',
  `searching` tinyint(1) NOT NULL COMMENT '是否用于搜索过滤,true或false',
  `segments` varchar(1000) DEFAULT '' COMMENT '数值类型参数,如果需要搜索,则添加分段间隔值,如CPU频率间隔:0.5-1.0',
  PRIMARY KEY (`id`),
  KEY `key_group` (`group_id`),
  KEY `key_category` (`cid`)
) ENGINE=InnoDB AUTO_INCREMENT=24 DEFAULT CHARSET=utf8 COMMENT='规格参数组下的参数名';


通用属性
用一个布尔类型字段来标记是否为通用:

  • generic来标记是否为通用属性:
    true:代表通用属性
    false:代表sku特有属性

搜索过滤
与搜索相关的有两个字段:

  • searching:标记是否用作过滤
    true:用于过滤搜索
    false:不用于过滤
    segments:某些数值类型的参数,在搜索时需要按区间划分,这里提前确定好划分区间
    比如电池容量,0 ~ 2000mAh,2000mAh ~ 3000mAh,3000mAh ~ 4000mAh

数值类型
某些规格参数可能为数值类型,这样的数据才需要划分区间,我们有两个字段来描述:

  • numberic:是否为数值类型
    true:数值类型
    false:不是数值类型
    unit:参数的单位

1.4 从面向对象角度分析

我们现在要表示规格参数的key,不要值。
如果用java对象表示:首先可以知道其可以分成组和组内属性两部分。组和组内属性分别是两类事物。
对组进行描述:对组内对共同特征进行提取,可以采用java中的类(class Group)进行描述。将主体、基本信息、操作系统等看成类中的name字段,将每一个分组下对应的多个参数(主体下的型号、入网年份)看成类中的List params,同时,每个组也会有一个属于自己的id来标识自己。

对组内属性进行描述:上述对List params又是一个新的事物,同样也可采用类(class Param)的概念进行描述。同样有name(参数名)、id字段,同时还要又一个groupId来说明这些参数属于哪一个组分类下的,这样就做到了Group表和Param表相互关联。同时,Group表中也应添加categoryId来说明这些规格组是属于哪个商品分类下的。但是,再考虑,规格参数属性里面应该也要加入是否为数值类型的字段;若是,那还会衍生出一个新的字段Unit;由于有一些规格参数的字段将来要做搜索的,因此还应该有判断该参数是否为搜索字段;如果可以搜索,同时又是数值类型,那还会有一个segment字段(字符串类型)。到此为止,规格参数就描述清楚了。

2. 商品表结构分析

全品类电商网站,商品种类繁多。每一件商品,其属性又有差别。为了更准确描述商品及细分差别,抽象出两个概念:SPU和SKU。

2.1 SKU和SPU

  • SPU:Standard Product Unit(标准产品单位),一组具有共同属性的商品集
  • SKU:Stock Keeping Unit(库存量单位),SPU商品集因具体特性不同而细分的每个商品

以图为例:


– 本页的华为Mate10就是一个商品集(SPU)
– 因为颜色、内存等不同,而细分出不同等Mate10,如亮黑色128G版。(SKU)

可以看出:

  • SPU是一个抽象等商品集概念,为了方便后台等管理
  • SKU才是具体要销售的商品,每一个SKU的价格、库存可能会不一样,用户购买的是SKU而不是SPU
  • 同一个SPU下的SKU有共有属性,也有特有属性

2.2 表结构

2.2.1 表结构分析

其实,SKU的特有属性是商品规格参数的一部分。规格参数我们已经提前设计好了,每一个分类都有自己的规格参数。因此我们在设计商品表的时候不要设计特有属性,这些特性设计到规格参数里了。因此规格参数表里的generic字段对特有属性还是通用属性进行了区分。

也就是说,我们没必要单独对SKU特有属性进行设计,它可以看做是规格参数中对一部分。这样规格参数中对属性可以标记成两部分:

  • 所有sku共享对规格属性(称为通用属性),我们记录在spu表中
  • 每个sku不同对规格属性(称为特有属性),我们记录在sku表中

2.2.2 SPU表

2.2.2.1 spu表结构

CREATE TABLE `tb_spu` (
  `id` bigint(20) NOT NULL AUTO_INCREMENT COMMENT 'spu id',
  `title` varchar(255) NOT NULL DEFAULT '' COMMENT '标题(商品名称)',
  `sub_title` varchar(255) DEFAULT '' COMMENT '子标题',
  `cid1` bigint(20) NOT NULL COMMENT '1级类目id',
  `cid2` bigint(20) NOT NULL COMMENT '2级类目id',
  `cid3` bigint(20) NOT NULL COMMENT '3级类目id',
  `brand_id` bigint(20) NOT NULL COMMENT '商品所属品牌id',
  `saleable` tinyint(1) NOT NULL DEFAULT '1' COMMENT '是否上架,0下架,1上架',
  `valid` tinyint(1) NOT NULL DEFAULT '1' COMMENT '是否有效,0已删除,1有效',
  `create_time` datetime DEFAULT NULL COMMENT '添加时间',
  `last_update_time` datetime DEFAULT NULL COMMENT '最后修改时间',
  PRIMARY KEY (`id`)
) ENGINE=InnoDB AUTO_INCREMENT=208 DEFAULT CHARSET=utf8 COMMENT='spu表,该表描述的是一个抽象的商品,比如 iphone8';

由于SPU表中某些字段的值非常大,比如商品描述。

因此为了不影响spu表的性能及查询速度,我们对表做了垂直拆分,将SPU的详情放到了另一张表:tb_spu_detail

注:

  • 垂直拆分:表字段过多,实现数据的解耦
  • 水平拆分:表数据量多(一张表有1万条数据,水平拆分成两个表,每个表5千条数据)数据库查询时做排序较麻烦,不过现在有很多中间件帮我们实现分表 查询、排序等。用在 订单表中,订单数据量很大,一张表存不下,必然要做水平拆分。

2.2.2.2 spu_detail 表结构

CREATE TABLE `tb_spu_detail` (
  `spu_id` bigint(20) NOT NULL,
  `description` text COMMENT '商品描述信息',
  `generic_spec` varchar(3000) NOT NULL DEFAULT '' COMMENT '通用规格参数数据',
  `special_spec` varchar(1000) NOT NULL COMMENT '特有规格参数及可选值信息,json格式',
  `packing_list` varchar(1000) DEFAULT '' COMMENT '包装清单',
  `after_service` varchar(1000) DEFAULT '' COMMENT '售后服务',
  PRIMARY KEY (`spu_id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8;

2.2.2.3 spu中对规格参数

前面讲过规格参数与商品分类绑定,一个分类下对所有SPU具有类似的规格参数。SPU下的SKU可能会有不同对规格参数,因此:

  • SPU中保存通用对规格参数信息。
  • SKU中保存特有规格参数。
2.2.2.3.1 generic_spec字段

generic_spec:保存通用规格参数信息的值,这里为了方便查询,使用了json格式。

json结构,其中都是键值对:

  • key:对应的规格参数的spec_param的id
  • value:对应规格参数的值
2.2.2.3.2 special_spec字段

由于SPU下的每一个sku,其值也都不一样,所以值会有很多,形成数组。
因此我们在SPU中,会把特有属性的所有值都记录下来,形成一个数组:


也是json结构:

  • key:规格参数id
  • value:spu属性的数组

问题:特有规格参数应该在sku中记录才对,为什么在spu中也要记录一份(聚合sku集合)?

  • 因为我们有时候需要把所有规格参数都查询出来,而不是只查询1个sku的属性。比如,商品详情页展示可选的规格参数时:

刚好符号我们的结构,这样页面渲染就非常方便了。

2.2.3 SKU表

2.2.3.1 sku表结构

CREATE TABLE `tb_sku` (
  `id` bigint(20) NOT NULL AUTO_INCREMENT COMMENT 'sku id',
  `spu_id` bigint(20) NOT NULL COMMENT 'spu id',
  `title` varchar(255) NOT NULL COMMENT 'sku特有标题',
  `images` varchar(1000) DEFAULT '' COMMENT '商品的图片,多个图片以‘,’分割',
  `price` bigint(15) NOT NULL DEFAULT '0' COMMENT '销售价格,单位为分',
  `indexes` varchar(100) COMMENT '特有规格属性在spu属性模板中的对应下标组合',
  `own_spec` varchar(1000) COMMENT 'sku的特有规格参数,json格式',
  `enable` tinyint(1) NOT NULL DEFAULT '1' COMMENT '是否有效,0无效,1有效',
  `create_time` datetime NOT NULL COMMENT '添加时间',
  `last_update_time` datetime NOT NULL COMMENT '最后修改时间',
  PRIMARY KEY (`id`),
  KEY `key_spu_id` (`spu_id`) USING BTREE
) ENGINE=InnoDB DEFAULT CHARSET=utf8 COMMENT='sku表,该表表示具体的商品实体,如黑色的64GB的iphone 8';

还有一张stock表,代表库存;
因为库存字段写频率较高,而SKU的其它字段(颜色、价格)以读为主,因此我们将两张表分离,读写不会干扰(垂直拆分)。

2.2.3.2 stock库存表

CREATE TABLE `tb_stock` (
  `sku_id` bigint(20) NOT NULL COMMENT '库存对应的商品sku id',
  `seckill_stock` int(9) DEFAULT '0' COMMENT '可秒杀库存',
  `seckill_total` int(9) DEFAULT '0' COMMENT '秒杀总数量',
  `stock` int(9) NOT NULL COMMENT '库存数量',
  PRIMARY KEY (`sku_id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8 COMMENT='库存表,代表库存,秒杀库存等信息';

2.2.3.3 sku中的规格参数

2.2.3.3.1 indexes字段
在SPU表中,已经对特有规格参数及可选项进行了保存,结构如下:

{
    "4": [
        "香槟金",
        "樱花粉",
        "磨砂黑"
    ],
    "12": [
        "2GB",
        "3GB"
    ],
    "13": [
        "16GB",
        "32GB"
    ]
}


这些特有属性如果排列组合,会产生12个不同的SKU,而不同的SKU,其属性就是上面备选项中的一个。
比如:

  • 红米4X,香槟金,2GB内存,16GB存储
  • 红米4X,磨砂黑,2GB内存,32GB存储

你会发现,每一个属性值,对应于SPUoptions数组的一个选项,如果我们记录下角标,就是这样:

  • 红米4X,0,0,0
  • 红米4X,2,0,1

既然如此,我们就可以将不同角标串联起来,作为SPU下不同SKU的标示。这就是我们的indexes字段。

这个设计在商品详情页(页面渲染)会特别有用:当用户点击选中一个特有属性,你就能根据 角标快速定位到sku。

2.2.3.3.2 own_spec字段

看结构:

{
    "4":"香槟金",
    "12":"2GB",
    "13":"16GB"
}
保存的是特有属性的键值对。
SPU中保存的是可选项,但不确定具体的值,而SKU中的保存的就是具体的值。

Scroll to Top