之前也写过类似的数据库设计方案,这一篇是为大家详细讲解参考京东商城围绕商品怎么来设计数据库,需要关注的细节很多,对字段进行详解,结合功能实现分析每一个字段设计的意义
大家看完这篇文章后可以看看前面四篇文章电商项目数据库设计方案,跟这一篇有点不同,之前是大多是参照苏宁易购来设计的
我们在设计数据库的时候,大多都是按照我们自己的实际需求来设计,提供这几篇仅为大家参考学习,能够举一反三参与到项目实战中,希望大家可以学到东西,同时,文章内容如果有错误的位置希望大家可以指正,共同学习。
接下来我们就言归正传,开始设计表
CREATE TABLE `tb_category` (
`id` bigint(20) NOT NULL AUTO_INCREMENT COMMENT '类目id',
`name` varchar(32) NOT NULL COMMENT '类目名称',
`parent_id` bigint(20) NOT NULL COMMENT '父类目id,顶级类目填0',
`is_parent` tinyint(1) NOT NULL COMMENT '是否为父节点,0为否,1为是',
`sort` tinyint(2) NOT NULL COMMENT '排序指数,越小越靠前',
`create_time` timestamp NULL DEFAULT CURRENT_TIMESTAMP COMMENT '数据创建时间',
`update_time` timestamp NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '数据更新时间',
PRIMARY KEY (`id`),
KEY `key_parent_id` (`parent_id`) USING BTREE
) ENGINE=InnoDB AUTO_INCREMENT=1424 DEFAULT CHARSET=utf8 COMMENT='商品类目表,类目和商品(spu)是一对多关系,类目与品牌是多对多关系'
截取京东首页,如下图:
左侧菜单栏,即显示的商品分类,分类表我们应该设置哪些字段,我们可以参照京东商城思考下:
首先作为关系型MySQL数据库,我们先定下通用字段,id、create_time、update_time这三个字段。逻辑删除字段看大家需求,我这里是自己做demo,就没有设置逻辑删除字段了,is_delete boolean类型,之前也写过仿照苏宁易购设计过数据库,大家可以参考着看,仅供学习使用。
看上图,我依次在菜单里面框出三个,在分类表中我们如何对菜单进行表示,字段名如何设置,很简单,设置成level1、level2、level3三个字段分别表示就可以了,level1表示一级菜单,level2表示二级菜单,level3表示三级菜单,但是想想,我们在实际业务中,如果我们开发一个商城以后有亿级用户,想添加其他的分类后,是不是要加level4、level5·····,这样设计就有点不灵活了
所以我们可以这样设计,设计一个parent_id 用来表示父类目id进行关联,顶级类目就是0,满足可扩展性,而且字段也不冗余,符合三范式设计,什么是数据库设计三范式,https://blog.csdn.net/weixin_42437102/article/details/106422548 不了解的同学可以看看这篇文章。
在设计一个is_parentid用来表示是否是父id,如果不是父id,肯定就是最后一次菜单,这里我不说是三级菜单,可能我们以后会进行扩展
给一个sort字段进行权重分配,排序指数,指数越小越靠前,这里就设计好了分类表。
CREATE TABLE `tb_brand` (
`id` bigint(20) NOT NULL AUTO_INCREMENT COMMENT '品牌id',
`name` varchar(64) NOT NULL COMMENT '品牌名称',
`image` varchar(256) DEFAULT '' COMMENT '品牌图片地址',
`letter` char(1) DEFAULT '' COMMENT '品牌的首字母',
`create_time` timestamp NULL DEFAULT CURRENT_TIMESTAMP,
`update_time` timestamp NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
PRIMARY KEY (`id`)
) ENGINE=InnoDB AUTO_INCREMENT=325403 DEFAULT CHARSET=utf8 COMMENT='品牌表,一个品牌下有多个商品(spu),一对多关系'
设计数据表,我们依旧先列出通用字段,id、create_time、update_time
截取京东页面
从上图我们可以看出,品牌设计也不怎么复杂,我们先看最上面,所有品牌,首字母表示,所有我们设计一个letter字段,用来表示品牌的首字母,可以用来做搜索
继续往下看,我们能看到什么?无非就会品牌的logo和名称了吧,那就设计name字段表示名称,image字段表示品牌的logo。还有没有什么?
逻辑删除也可以设置,这个放在关联关系表来说!
前面我们已经分析出了商品分类表和品牌表,他们之间有什么关系,我们这个阶段来屡屡
首先我们还是回到京东首页,也就是我们在设计分类表的时候截取的京东那张图片,对边点击一个三级分类,我这里点的手机,是不是看到的是品牌表这里的图片,也就是说,一个分类下有多个品牌,如:(手机:有华为手机,苹果手机,小米手机···)
接下来看一张图:
华为品牌下也有很多分类,所以一个品牌下也有很多分类
即分类表与品牌表是多对多的关系,多对多设计中间表
CREATE TABLE `tb_category_brand` (
`category_id` bigint(20) NOT NULL COMMENT '商品类目id',
`brand_id` bigint(20) NOT NULL COMMENT '品牌id',
PRIMARY KEY (`category_id`,`brand_id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8 COMMENT='商品分类和品牌的中间表,两者是多对多关系'
但是,你可能会发现,这张表中并没有设置外键约束,似乎与数据库的设计范式不符。为什么这么做?
在电商行业,性能是非常重要的。我们宁可在代码中通过逻辑来维护表关系,也不设置外键。
如果使用逻辑删除是否可以解决这个问题,大家可以思考下。
商品参数表也是我们围绕商品进行表设计的一个必不可少的表设计,但是参数表改如何设计呢?按照我们的正常思维,一个商品有很多参数,比如一个手机,有品牌、产品名称、机身长度、机身重量、CPU、内存等等一系列,我们分别设计成字段,一张表也就几十个字段,但是想想,这样做会有什么不妥,首先,我们设计数据库是设计一个全品类的电商平台,商品有很多种,我们打开京东看看,
手机的规格与包装
空调的规格与包装
还有很多很多,如果都设计一张参数表,可想而知是不妥的吧
但是我们发现,虽然不同商品,规格不同。但是同一分类下的商品,比如都是手机,其规格参数名称是一致的,但是值不一样。
也就是说,商品的规格参数应该是与分类绑定的。每一个分类都有统一的规格参数模板,但不同商品其参数值可能不同。
因此:
所以我们引入参数组与参数表
值我们暂且先不管,新增商品时,再来填写规格参数值即可,我们先思考规格参数模板(key)该如何设计。
从面向对象的思想来看,我们规格参数和规格组分别是两类事务,并且组与组内参数成一对多关系,因此可以有两个类分别描述他们,那么从数据库设计来看,也就对应两张不同的表:
如图:
大家接下来要思考的就是:
想清楚上面的问题,就知道表该怎么设计了。
CREATE TABLE `tb_spec_group` (
`id` bigint(20) NOT NULL AUTO_INCREMENT COMMENT '主键',
`category_id` bigint(20) NOT NULL COMMENT '商品分类id,一个分类下有多个规格组',
`name` varchar(32) NOT NULL COMMENT '规格组的名称',
`create_time` timestamp NULL DEFAULT CURRENT_TIMESTAMP,
`update_time` timestamp NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
PRIMARY KEY (`id`),
KEY `key_category` (`cid`)
) ENGINE=InnoDB AUTO_INCREMENT=15 DEFAULT CHARSET=utf8 COMMENT='规格参数的分组表,每个商品分类下有多个规格参数组';
还是老套路:固定的三个字段,id、create_time、update_time
组名:name,因为一个商品分类下有多个规格参数组,所以一对多的关系,设计category_id关联实现一对多
先看表设计
CREATE TABLE `tb_spec_param` (
`id` bigint(20) NOT NULL AUTO_INCREMENT COMMENT '主键',
`category_id` bigint(20) NOT NULL COMMENT '商品分类id',
`group_id` bigint(20) NOT NULL,
`name` varchar(128) NOT NULL COMMENT '参数名',
`numeric` tinyint(1) NOT NULL COMMENT '是否是数字类型参数,true或false',
`unit` varchar(128) DEFAULT '' COMMENT '数字类型参数的单位,非数字类型可以为空',
`generic` tinyint(1) NOT NULL COMMENT '是否是sku通用属性,true或false',
`searching` tinyint(1) NOT NULL COMMENT '是否用于搜索过滤,true或false',
`segments` varchar(1024) DEFAULT '' COMMENT '数值类型参数,如果需要搜索,则添加分段间隔值,如CPU频率间隔:0.5-1.0',
`create_time` timestamp NULL DEFAULT CURRENT_TIMESTAMP,
`update_time` timestamp NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
PRIMARY KEY (`id`),
KEY `key_group` (`group_id`),
KEY `key_category` (`cid`)
) ENGINE=InnoDB AUTO_INCREMENT=24 DEFAULT CHARSET=utf8 COMMENT='规格参数组下的参数名';
固定三个字段我们不需要过多的解释,这是我们的套路:id、create_time、update_time。
category_id、group_id 一个商品分类有多个规格参数组,一个规格参数组有多个规格参数
name 参数名也好解释
接下来剩numeric、unit、generic、searching、segments
我们有两个字段来描述:有的参数值是数字类型,带单位,有的不是,所以我们设计这个字段作为一个标识、unit作为参数值的单位。
你会发现,过滤条件中的屏幕尺寸、运行内存、网路、机身内存、电池容量、CPU核数等,在规格参数中都能找到:
也就是说,规格参数中的数据,将来会有一部分作为搜索条件来使用。我们可以在设计时,将这部分属性标记出来,将来做搜索的时候,作为过滤条件。
与搜索相关的有两个字段:
一个全品类的电商网站,因此商品的种类繁多,每一件商品,其属性又有差别。为了更准确描述商品及细分差别,抽象出两个概念:SPU和SKU,了解一下:
有一个generic属性,代表通用属性,我们在商品数据结构时再聊。
了解SPU和SKU是啥
SPU:Standard Product Unit (标准产品单位) ,一组具有共同属性的商品集
SKU:Stock Keeping Unit(库存量单位),SPU商品集因具体特性不同而细分的每个商品
以图为例来看:
可以看出:
弄清楚了SPU和SKU的概念区分,接下来我们就着手开始设计商品表SPU和SKU了。
我们先看看京东页面是怎么设计的,我们随便打开一个商品
看着这个图片,我们自己来模拟设计下SPU表
似乎并不复杂.
再看下SKU,大家觉得应该有什么字段?
id: 主键
spu_id: 关联的spu
price: 价格
images: 图片
stock: 库存
颜色?
内存?
硬盘?
sku的特有属性也是变化的,不同商品,特有属性不一定相同,那么我们的表字段岂不是不确定?
sku的这个特有属性该如何设计呢?
首先我们应该了解sku特有属性为何?
顾名思义,特有属性就是特有的,所有商品各自持有的,比如商品的内存,不同手机内存不一样,有4G、6G、8G、16G…,颜色有红色、绿色、黑色等等,
那么是不是就是说我们的sku就是跟商品分类有关系,在我这里,SKU特有属性是跟商品规格参数有关系的,我们还是截取京东页面来看看,如下图
这都是在参数规格中可以看到的,点击哪一种,下面的参数就跟着变化。SKU特有属性是商品参数规格的一部分
也就是说,我们没必要单独对SKU的特有属性进行设计,它可以看做是规格参数中的一部分。这样规格参数中的属性可以标记成两部分:
回一下之前我们设计的规格参数表,是不是有一个字段,名为generic,标记通用和特有属性。就是为了这里使用。
这样以来,商品SKU表就只需要设计规格属性以外的其它字段了,规格属性由之前的规格参数表来保存。
但是,规格属性的值依然是需要与商品相关联的。接下来我们就来看看到底怎么设计
CREATE TABLE `tb_spu` (
`id` bigint(20) NOT NULL AUTO_INCREMENT COMMENT 'spu id',
`name` varchar(256) NOT NULL DEFAULT '' COMMENT '商品名称',
`sub_title` varchar(256) 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上架',
`create_time` timestamp NULL DEFAULT CURRENT_TIMESTAMP COMMENT '添加时间',
`update_time` timestamp NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '最后修改时间',
PRIMARY KEY (`id`)
) ENGINE=InnoDB AUTO_INCREMENT=187 DEFAULT CHARSET=utf8 COMMENT='spu表,该表描述的是一个抽象性的商品,比如 iphone8'
与我们前面分析的基本类似,但是似乎少了一些字段,比如商品描述。
我们做了表的垂直拆分,将SPU的详情放到了另一张表:tb_spu_detail
CREATE TABLE `tb_spu_detail` (
`spu_id` bigint(20) NOT NULL,
`description` text COMMENT '商品描述信息',
`generic_spec` varchar(2048) NOT NULL DEFAULT '' COMMENT '通用规格参数数据',
`special_spec` varchar(1024) NOT NULL COMMENT '特有规格参数及可选值信息,json格式',
`packing_list` varchar(1024) DEFAULT '' COMMENT '包装清单',
`after_service` varchar(1024) DEFAULT '' COMMENT '售后服务',
`create_time` timestamp NULL DEFAULT CURRENT_TIMESTAMP,
`update_time` timestamp NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
PRIMARY KEY (`spu_id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8
这张表中的数据都比较大,为了不影响主表的查询效率我们拆分出这张表。
需要注意的是这两个字段:generic_spec和special_spec。
Create Table
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(256) NOT NULL COMMENT '商品标题',
`images` varchar(1024) DEFAULT '' COMMENT '商品的图片,多个图片以‘,’分割',
`stock` int(8) unsigned DEFAULT '9999' COMMENT '库存',
`price` bigint(16) NOT NULL DEFAULT '0' COMMENT '销售价格,单位为分',
`indexes` varchar(32) DEFAULT '' COMMENT '特有规格属性在spu属性模板中的对应下标组合',
`own_spec` varchar(1024) DEFAULT '' COMMENT 'sku的特有规格参数键值对,json格式,反序列化时请使用linkedHashMap,保证有序',
`enable` tinyint(1) NOT NULL DEFAULT '1' COMMENT '是否有效,0无效,1有效',
`create_time` timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT '添加时间',
`update_time` timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '最后修改时间',
PRIMARY KEY (`id`),
KEY `key_spu_id` (`spu_id`) USING BTREE
) ENGINE=InnoDB AUTO_INCREMENT=27359021564 DEFAULT CHARSET=utf8 COMMENT='sku表,该表表示具体的商品实体,如黑色的 64g的iphone 8'
接下来对SPU表和SKU表字段进行下详细的说明
我们先来看SPU表,这个表应该没有什么多大的问题,就是放一些共有的共有的字段,对cid说明一下吧,为什么设计了三个分类id字段,我们表直接比有关联吗?直接一个分类id就可以找到其他的分类id,这样想是没有问题的,我们主要是为了更好的页面显示才这样设计的,大家也可以自行优化。然后SPU表控制商品的上下架,SPU表控制商品是否有效,比如没有一个SKU下的商品没有库存不就无效了,因为我们一个SPU是包含多个SKU的,所以这样设计
我们之所以把SPU表在分解一个详情表出来的原因就是考虑到性能的问题,把一些大字段单独分出来,也就是垂直拆分,这样可以提高数据检索效率
商品详情表我们主要讲解两个字段
前面讲过规格参数与商品分类绑定,同一分类的商品,会有一套相同的规格参数key(规格参数模板),但是这个分类下每个商品的规格参数值都不相同,因此要满足下面几点:
我们有一个规格参数表,跟分类关联,保存的就是某分类下的规格参数模板。
我们还需要表,跟商品关联,保存某个商品,相关联的规格参数的值。
规格参数因为分成了通用规格参数和特有规格参数,因此规格参数值也需要分别于SPU和SKU关联:
但是我们并没有增加新的表,来看下我们的 表如何存储这些信息。
generic_spec字段
如果要设计一张表,来表示spu中的通用规格属性的值,至少需要下面的字段:
我们并没有这么设计。而是把与某个商品相关的规格属性值,直接保存到这个商品spu表中,因此这些规格属性关联的商品就一目了然,那么上述3个属性中的spu_id就无需保存了,而剩下的就是param_id和规格参数值了。两者刚好是一一对应关系,组成一个键值对。我们刚好可以用一个json结构来标示。
是也就是spuDetail表中的generic_spec,其中保存通用规格参数信息的值:
整体来看:
# tb_spu_detail表中通用属性:generic_spec(spu_id = 2) 与 param表中的id一一对应
{
"1": "华为",
"2": "G9青春版(全网通版)",
"3": 2016,
"5": 143,
"6": "陶瓷",
"7": "Android",
"8": "骁龙(Snapdragon)",
"9": "骁龙617(msm8952)",
"10": "八核",
"11": 1.5,
"14": 5.2,
"15": "1920*1080(FHD)",
"16": 800,
"17": 1300,
"18": 3000
}
json结构,其中都是键值对:
special_spec字段
我们说spu中只保存通用规格参数,那么为什么有多出了一个special_spec字段呢?
以手机为例,品牌、操作系统等肯定是通用规格属性,内存、颜色等肯定是特有属性。
当你确定了一个SPU,比如小米的:红米4X,因为颜色内存等不同,会形成多个sku。如果把每个sku的颜色、内存等信息都整理一下,会形成下面的结果:
也就是说这里把一个spu下的每个sku的特有规格属性值聚合在了一起!这个就是special_spec字段了。
来看数据格式:
# tb_spu_detail表中特殊属性:special_spec(spu_id = 2) 与 tb_sku表中的id一一对应
{
"4": ["白色", "金色", "玫瑰金"],
"12": ["3GB"],
"13": ["16GB"]
}
也是json结构:
那么问题来:为什么要在spu中把所有sku的规格属性聚合起来保存呢?
因为我们有时候需要把所有规格参数都查询出来,而不是只查询1个sku的属性。比如,商品详情页展示可选的规格参数时:
刚好符号我们的结构,这样页面渲染就非常方便了。
综上所述,spu与商品规格参数模板的关系如图所示:
SPU表弄清楚了,在讲解SKU表字段的时候就很容易理解了
SKU主要就是这三个字段,其他的字段大家看一眼就应该明白了
我们先讲own_spec字段,描述的SKU的特有规格参数键值对,是一个json 数组格式,而own_spec字段也是一个json数据,描述的是一个商品具体的规格参数
看数据
{
"4": "白色",
"12": "3GB",
"13": "16GB"
}
保存的是特有属性的键值对。
SPU中保存的是可选项,但不确定具体的值,而SKU中的保存的就是具体的值。
接下来讲讲indexes字段,特有规格属性在spu属性模板中的对应下标组合
在SPU表中,已经对特有规格参数及可选项进行了保存,结构如下:
# tb_spu_detail表中特殊属性:special_spec(spu_id = 2) 与 tb_sku表中的id一一对应
{
"4": ["白色", "金色", "玫瑰金"],
"12": ["3GB"],
"13": ["16GB"]
}
比如:
这些特有属性进行排列组合就会有三种,如果在可选项中再添加其他的,排列组合的方式就会更多,所以我们就可以记录每组可选项的下表进行标记,这样可以做到当用户点击哪一个可选项,我们就可以快速定位到SKU。
而这一部分就是我们的own_spec
enable字段就是控制具体的一个商品的规格是否有效
我们设计了哪几张表,有什么关系
关系: