淘宝的电商产品种类非常丰富,必然得力于其商品模型的高度通用性和扩展性。
下面我将亲自操作淘宝商品的发布过程,结合网上其他博客对淘宝网商品库的分析,简单谈谈我的理解。
注:下面不特殊说明,各个表除主键外的无需建立其他唯一索引。
品类
在淘宝网发布宝贝,需要先选择所属的品类:
我要卖的商品,属于『流行男装 -> 帆布鞋 -> YINGLUNKUANGWEI/英伦匡威 』这个品类。可见,品类存在层级关系,流行男装是帆布鞋的父品类,英伦匡威属于帆布鞋的子品类,同时英伦匡威也是一个品牌。
撇开品牌不谈,维护这样一个品类关系,应该有这样一个表结构:
品类ID | 主键 |
父品类ID | 父级品类ID |
品类名称 | 例如:帆布鞋 |
品类权重 | 上图每一列展示有次序先后之分 |
商品
选好品类好,点击编辑宝贝,最上面是这样的:
也就是说,你要编辑一个商品的名称和描述,并且商品属于一个分类,因此商品表大概如此:
商品ID | 主键 |
品类ID | 最子品类,比如:衣服->毛衣,那么这里应该是毛衣品类ID |
标题 | 例如:一个YY出来的匡威鞋 |
描述 | 例如:买不了吃亏,买不了上当 |
属性和属性值
宝贝属性部分,淘宝规定了若干必填项和选填项,其中必填项一般用来商品检索用途,比如进入检索页可以看到"鞋帮高度"和"闭合方式"的筛选项:
我们知道,淘宝售卖各式各样的商品,需要支持各式各样的属性信息,比如:尺寸,颜色...等等。同样的道理,单单就颜色这个属性来说,其对应的属性值也可能是:绿色,红色...等等。
因此,我们可以用属性表存储属性,用属性值表存储与属性相关联的可选值,例如下面:
属性表
属性ID | 主键 |
属性名称 | 例如:颜色 |
属性值表
属性值ID | 主键 |
所属的属性ID | 例如:归属于 颜色 属性 |
属性值 | 例如:红色 |
有这两个表之后,可以找一个运营人员小C专门负责接属性&属性值的增删改查需求,比如:
负责鞋品类的运营小A找小C说:颜色下面给我加一个彩虹色。
负责车品类的运营小B找小C说:颜色下面给我加一个蓝色。
在小C编辑完成后,小A可以给自己的品类引入:颜色 -> 彩虹色的选项,小B可以给自己的品类引入:颜色->蓝色的选项。
上面这段工作场景,其实引申出了接下来要谈到的另一个关键点,就是品类和属性&属性值的关系。
品类和属性&属性值
前面我们谈到小C的工作职责,是接小A和小B的需求,其中小A负责鞋品类,小B负责车品类。现在小C维护的数据中,颜色属性有2个值:彩虹色,蓝色,但是小A的品类只需要彩虹色,小B的品类只需要蓝色,如果他们俩去查询小C的属性值库会得到彩虹色,蓝色两个选项,这将导致一个淘宝卖家在编辑一辆汽车的时候可以选择彩虹色,可是我还没见过汽车是彩虹色喷漆的呢!
问题就在于,小C维护的是整个淘宝所有的属性与属性值,而小A和小B需要的仅仅所有属性中的部分,以及属性对应的属性值中的部分,因此我们需要维护一个"品类"与"属性","属性值"之间的关系,从而形成一个子集。
我们先建一个"品类属性"关系表,来标明某个品类关联到哪些属性子集合:
品类属性模板ID | 主键 | |
品类ID | 例如:帆布鞋的 品类ID | |
属性ID | 例如:颜色的 属性ID | |
是否必填 | 之前的图片里,带*号的属性 |
再来建一个"品类属性值"关系表,来标明某个品类某个属性关联到哪些属性值子集合:
品类属性模板ID | 例如:上面那个表的主键 |
属性值ID | 例如:蓝色 的属性值ID |
经过这样的设计,我通过先选择品类,就可以通过品类ID查询上述2个表得到属性子集合和属性值子集合,也就是最上面看到的各种"宝贝属性"的数据来源了。
PS:品类属性模板表没有对"品类ID+属性ID"做唯一性限制,这是出于真实运营场景下灵活性的考虑,同一时刻相同"品类ID+属性ID"应该只有一条记录处于有效状态,运营可以灵活的切换,这是程序来保障的,当然运营同学也不应该留存太多用不到的品类属性模板。
另外提一下,因为品类有父子关系,所以通常子品类会继承父品类的品类属性与品类属性值,这个不是关注的重点。
SKU
上面勾选的这些属性,其实都是为了描述商品的一些特性,然而我们在实际购买的时候其实并不是购买商品自身,而是购买一个SKU,那么SKU是什么?和商品是什么关系?
继续之前的编辑页面,向下滚动进入这个区域:这里是编辑宝贝的规格,这里的规格其实背后对应的就是SKU的概念
详细来说:
- 颜色分类和尺码是2个品类属性,它们和之前勾选的其他属性相比,数据来源一样,但是用途不同,这个后面会说。
- 选颜色时候,黑色和柠檬黄是2个品类属性值,选尺码的时候48.5和49.5是品类属性值。
经过程序的排列组合,产生了如下几种组合关系:
这里将颜色和尺码组合后,产生了4行记录,每一行记录就称作一个SKU,在购买时的效果我们应该很熟悉:
每一种尺码和颜色的组合选定之后,价格等信息都随之变动,这就是选择了一个SKU,也就是我们真实购买的东西,因此尺码和颜色分类这两种属性我们也称为销售属性(应该给"品类属性"表添加一个"是否为销售属性"),也就是控制SKU选择的属性。
根据上面的分析,SKU首先应该属于一个商品,也就是上面的购买页面代表一个商品,上面的多选按钮可以指定一个SKU,商品和SKU之间有1:N的关系。
其次,SKU应该与属性以及属性值有关联,比如:37码-绿色,39码-咖啡色,也就说:"尺码属性:37码,颜色分类:绿色"以及"尺码属性:39码,颜色属性:咖啡色"。
SKU表
SKU ID | 主键 |
商品ID | 所属的商品ID,例如:静熙男士帆布鞋 的商品ID |
价格 | SKU的售价 |
SKU销售属性表(也可以打包放在sku表的一个字段里)
SKU ID | 联合主键 | 一个SKU一个属性只能对应一个值 |
属性ID | ||
属性值ID |
库存
库存系统本身是个独立于商品库外的系统,通常要求较高的处理能力,这里仅说明库存应该与SKU关联:
SKU ID | 主键 |
剩余库存 | 例如:10个 |
说在最后
既然一个商品库,需要维护那么多表,那么表之间的一致性就是很大的问题。
举几个例子:
- 新建商品:正常来说应该保存1个商品记录,4个sku记录,4个库存记录,以及若干其他表的信息,如果开启数据库事务可以保证要么都成功,要么都失败。但是我们知道淘宝这种规模一定是分布式的mysql部署,另外,商品和库存是2个系统,一般也不会共享数据库,这些都导致单机事务的做法是行不通的。通常来说,都是考虑最终一致性,或者说不一致不会导致错误,比如:先插入库存记录,再插入sku记录,最后插入商品记录,这样就算前一步失败也可以保证用户搜索不到商品,不会导致致命错误。
- 修改商品:修改商品,除了改商品自身的描述信息,还可能对sku进行增删改查。如果一个商品2个人同时改,那么有可能导致2个人提交瞬间,请求各种交错,谁都得不到想要的结果,因此一般可以考虑用数据库锁住商品记录,然后对sku信息进行独占的查询与修改,这样可以保证2个人串行生效,任何一方提交时发现现在的数据在编辑期间发生了变化,都可以回滚事务,重新查询最新状态。
另外,我们也不应该执行数据库的delete命令来删除属性,属性值,或者任何其他信息,因为极有可能已有商品对其产生了依赖,必须采用逻辑删除,也就是标记删除的方法,保证已有商品就算依赖了删除的属性也可以正常售卖,直到商家主动的重新编辑商品时重新选择商品的属性即可。
如果喜欢,请关注我。