电商项目数据库设计 | 参考京东商城详细讲解商品数据库设计
之前也写过类似的数据库设计方案,这一篇是为大家详细讲解参考京东商城围绕商品怎么来设计数据库,需要关注的细节很多,对字段进行详解,结合功能实现分析每一个字段设计的意义
大家看完这篇文章后可以看看前面四篇文章电商项目数据库设计方案,跟这一篇有点不同,之前是大多是参照苏宁易购来设计的
我们在设计数据库的时候,大多都是按照我们自己的实际需求来设计,提供这几篇仅为大家参考学习,能够举一反三参与到项目实战中,希望大家可以学到东西,同时,文章内容如果有错误的位置希望大家可以指正,共同学习。
接下来我们就言归正传,开始设计表
分类表
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,满足可扩展性,而且字段也不冗余,符合三范式设计,什么是数据库设计三范式,blog.csdn.net/weixin_4243… 不了解的同学可以看看这篇文章。
在设计一个 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)与值(value)应该分开来保存
- 一个分类,对应一套规格参数模板,只有规格参数 key,没有值
- 一个分类对应多个商品,每个商品的规格值不同,每个商品对应一套规格的值
所以我们引入参数组与参数表
值我们暂且先不管,新增商品时,再来填写规格参数值即可,我们先思考规格参数模板(key)该如何设计。
- 规格数据首先要分组,组内再有不同的规格参数
- 一个分类规格模板中,有多个规格组
- 每个规格组中,包含多个规格参数
从面向对象的思想来看,我们规格参数和规格组分别是两类事务,并且组与组内参数成一对多关系,因此可以有两个类分别描述他们,那么从数据库设计来看,也就对应两张不同的表:
- 规格组:tb_spec_group
- 一个商品分类下有多个规格组
- 规格参数:tb_spec_param
- 一个规格组下,有多个规格参数
如图:
大家接下来要思考的就是:
- 描述规格组需要哪些属性?
- 因为商品分类表与规格组表是一对多的关系,一个商品分类里面有多个组,所以在商品规格组表里面要有商品分类 id 关联起来
- 所以字段可以设置为以下:
- id 规格组自增 id
- category_id 商品分类 id
- name 组名
- create_time 创建时间
- update_time 更新时间
- 描述规格参数需要哪些属性? 规格组表与与规格参数表也是一对多的关系,即一个组有多个参数,所以需要关联商品分类 id,规格组 id
想清楚上面的问题,就知道表该怎么设计了。
参数组表
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
- 数值类型:numeric
我们有两个字段来描述:有的参数值是数字类型,带单位,有的不是,所以我们设计这个字段作为一个标识、unit 作为参数值的单位。
- searching、segments 是用来做搜索用的, 我们随便打开一个页面
你会发现,过滤条件中的屏幕尺寸、运行内存、网路、机身内存、电池容量、CPU 核数等,在规格参数中都能找到:
也就是说,规格参数中的数据,将来会有一部分作为搜索条件来使用。我们可以在设计时,将这部分属性标记出来,将来做搜索的时候,作为过滤条件。
与搜索相关的有两个字段:
- searching:标记是否用作过滤
- true:用于过滤搜索
- false:不用于过滤
- segments:某些数值类型的参数,在搜索时需要按区间划分,这里提前确定好划分区间
- 比如电池容量,0-2000mAh,2000mAh-3000mAh,3000mAh-4000mAh
一个全品类的电商网站,因此商品的种类繁多,每一件商品,其属性又有差别。为了更准确描述商品及细分差别,抽象出两个概念:SPU 和 SKU,了解一下:
有一个 generic 属性,代表通用属性,我们在商品数据结构时再聊。
了解 SPU 和 SKU 是啥
SPU:Standard Product Unit (标准产品单位) ,一组具有共同属性的商品集
SKU:Stock Keeping Unit(库存量单位),SPU 商品集因具体特性不同而细分的每个商品
以图为例来看:
- 本页的 华为 Mate10 就是一个商品集(SPU)
- 因为颜色、内存等不同,而细分出不同的 Mate10,如亮黑色 128G 版。(SKU)
可以看出:
- SPU 是一个抽象的商品集概念,为了方便后台的管理。
- SKU 才是具体要销售的商品,每一个 SKU 的价格、库存可能会不一样,用户购买的是 SKU 而不是 SPU
弄清楚了 SPU 和 SKU 的概念区分,接下来我们就着手开始设计商品表 SPU 和 SKU 了。
我们先看看京东页面是怎么设计的,我们随便打开一个商品
看着这个图片,我们自己来模拟设计下 SPU 表
- id 主键
- c_id 分类 ID
- brand_id 品牌 ID
- name 商品名称
- description 描述
- spec 规格
- after_service 售后服务
- comment 评价
- …
似乎并不复杂.
再看下 SKU,大家觉得应该有什么字段? id: 主键 spu_id: 关联的 spu price: 价格 images: 图片 stock: 库存 颜色? 内存? 硬盘?
sku 的特有属性也是变化的,不同商品,特有属性不一定相同,那么我们的表字段岂不是不确定?
sku 的这个特有属性该如何设计呢?
首先我们应该了解 sku 特有属性为何?
顾名思义,特有属性就是特有的,所有商品各自持有的,比如商品的内存,不同手机内存不一样,有 4G、6G、8G、16G…..,颜色有红色、绿色、黑色等等,
那么是不是就是说我们的 sku 就是跟商品分类有关系,在我这里,SKU 特有属性是跟商品规格参数有关系的,我们还是截取京东页面来看看,如下图
- 颜色 —>对应五种
- 内存 —>对应三种
这都是在参数规格中可以看到的,点击哪一种,下面的参数就跟着变化。SKU 特有属性是商品参数规格的一部分
也就是说,我们没必要单独对 SKU 的特有属性进行设计,它可以看做是规格参数中的一部分。这样规格参数中的属性可以标记成两部分:
- spu 下所有 sku 共享的规格属性(称为通用属性)
- spu 下每个 sku 不同的规格属性(称为特有属性)
回一下之前我们设计的规格参数表,是不是有一个字段,名为 generic,标记通用和特有属性。就是为了这里使用。
这样以来,商品 SKU 表就只需要设计规格属性以外的其它字段了,规格属性由之前的规格参数表来保存。
但是,规格属性的值依然是需要与商品相关联的。接下来我们就来看看到底怎么设计
SPU 表
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。
SKU 表
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 表在分解一个详情表出来的原因就是考虑到性能的问题,把一些大字段单独分出来,也就是垂直拆分,这样可以提高数据检索效率
商品详情表我们主要讲解两个字段
- generic_spec:通用规格参数数据
- special_spec:特有规格参数及可选值信息,json 格式
前面讲过规格参数与商品分类绑定,同一分类的商品,会有一套相同的规格参数 key(规格参数模板),但是这个分类下每个商品的规格参数值都不相同,因此要满足下面几点:
- 我们有一个规格参数表,跟分类关联,保存的就是某分类下的规格参数模板。
- 我们还需要表,跟商品关联,保存某个商品,相关联的规格参数的值。
- 规格参数因为分成了通用规格参数和特有规格参数,因此规格参数值也需要分别于 SPU 和 SKU 关联:
- 通用的规格参数值与 SPU 关联。
- 特有规格参数值与 SKU 关联。
但是我们并没有增加新的表,来看下我们的 表如何存储这些信息。
generic_spec 字段
如果要设计一张表,来表示 spu 中的通用规格属性的值,至少需要下面的字段:
- spu_id: 与哪个商品关联
- param_id:是商品的哪个规格参数
- value: 具体的值
我们并没有这么设计。而是把与某个商品相关的规格属性值,直接保存到这个商品 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 结构,其中都是键值对:
- key:对应的规格参数的 spec_param 的 id
- value:对应规格参数的值
special_spec 字段
我们说 spu 中只保存通用规格参数,那么为什么有多出了一个 special_spec 字段呢?
以手机为例,品牌、操作系统等肯定是通用规格属性,内存、颜色等肯定是特有属性。
当你确定了一个 SPU,比如小米的:红米 4X,因为颜色内存等不同,会形成多个 sku。如果把每个 sku 的颜色、内存等信息都整理一下,会形成下面的结果:
- 颜色:[白色, 金色, 玫瑰金]
- 内存:[2G, 3G]
- 机身存储:[16GB, 32GB]
也就是说这里把一个 spu 下的每个 sku 的特有规格属性值聚合在了一起!这个就是 special_spec 字段了。
来看数据格式:
# tb_spu_detail表中特殊属性:special_spec(spu_id = 2) 与 tb_sku表中的id一一对应
{
"4": ["白色", "金色", "玫瑰金"],
"12": ["3GB"],
"13": ["16GB"]
}
也是 json 结构:
- key:规格参数 id
- value:spu 属性的数组
那么问题来:为什么要在 spu 中把所有 sku 的规格属性聚合起来保存呢?
因为我们有时候需要把所有规格参数都查询出来,而不是只查询 1 个 sku 的属性。比如,商品详情页展示可选的规格参数时:
刚好符号我们的结构,这样页面渲染就非常方便了。
综上所述,spu 与商品规格参数模板的关系如图所示:
SPU 表弄清楚了,在讲解 SKU 表字段的时候就很容易理解了
- indexes: 特有规格属性在 spu 属性模板中的对应下标组合
- own_spec:sku 的特有规格参数键值对,json 格式,反序列化时请使用 linkedHashMap,保证有序
- enable: 是否有效
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"]
}
比如:
- Nova 5,白色,3GB,16GB:0_0_0
- Nova 5,金色,3GB,16GB:1_0_0
- Nova 5,玫瑰金,3GB,16GB:2_0_0
这些特有属性进行排列组合就会有三种,如果在可选项中再添加其他的,排列组合的方式就会更多,所以我们就可以记录每组可选项的下表进行标记,这样可以做到当用户点击哪一个可选项,我们就可以快速定位到 SKU。
而这一部分就是我们的 own_spec
enable 字段就是控制具体的一个商品的规格是否有效
总结
我们设计了哪几张表,有什么关系
- 分类表: tb_category
- 品牌表: tb_brand
- 分类品牌表:tb_category_brand
- 规格组表: tb_spec_group
- 规格参数表:tb_spec_param
- SPU 表: tb_spu
- SPU 详情表: tb_spu_detail
- SKU 表: tb_sku
关系:
- 一个分类有多个品牌,一个品牌属于多个分类,所以是多对多
- 一个分类有多个规格组,一个规格组有多个规格参数,所以是一对多
- 一个分类下有多个 SPU,所以是一对多
- 一个品牌下有多个 SPU,所以是一对多
- 一个 SPU 下有多个 SKU,所以是一对多