首页 / 资讯中心 / 文章详情

SPU、SKU与ID的区别及电商商品数据模型设计

SPU、SKU与ID的区别及电商商品数据模型设计 ★ FEATURED ARTICLE
在做电商系统的这两年里我见过不少刚入行的同学包括当年的我自己在SPU、SKU、ID三个词之间反复打转。后台商品列表一个字段一个字段往下填运营跑来问这个SPU价格是不是改错了开发在工单里写SKU库存扣减失败两边说的东西好像是一回事又明显不是一回事。其实这些概念拆开看非常直白SPU是标准产品单元SKU是库存保有单位ID是唯一的标识。搞懂它们不是为了背名词而是为了以后设计商品数据、排查库存和价格问题时不至于抓瞎。这篇就按我自己的理解把三者什么意思、怎么区别、怎么关联一次说清楚适合刚碰电商的产品、运营和开发同事也适合想系统梳理商品主数据的同学。1. 先从一个真实的翻车现场说起把SPU和SKU混在一起会出什么事几年前我参与一个电商后台的改版当时商品列表页一直有个奇怪的现象运营在Excel里批量改价选中一行填了新价格保存后发现同款商品下好几条不同颜色的记录价格全变了。第一次遇到时大家都以为是Excel模板的问题后来查了数据库才发现运营表格里的SPU编码列实际填的是SKU编码而系统批处理接口是按SPU维度去覆盖价格的。这个事故的本质就是概念没对齐。SPU是商品的大类比如iPhone 15 Pro MaxSKU是这个大类下面更具体的售卖单元比如iPhone 15 Pro Max 256G 原色钛金属。运营想改的是某个具体颜色和容量的价格也就是某个SKU的价格但她在表格里用了SPU这一层的关键字段程序一执行整条SPU下的所有SKU全部跟着变动差价的损失当时算了半天才理清楚。从那以后我就养成了一个习惯在任何商品系统的文档、接口、表结构设计里第一件事先明确字段到底挂在SPU上还是SKU上宁可多写注释也不要默认别人能猜对。很多看起来像是数据没同步的问题最后查下来都是这个层次没分清楚。电商系统里商品主数据是所有业务的地基库存、价格、订单、售后全部从这里长出来地基的层级定义错了后面每一层都要跟着遭殃。这个翻车经历也正好引出本文要回答的三个问题SPU和SKU到底各自代表什么ID在这个体系里扮演什么角色三者之间的关系该怎么用一句话跟同事对齐2. SPU、SKU、ID分别是什么从定义到生活化类比2.1 SPU标准化产品单元管的是一件商品的共同身份SPU全称是Standard Product Unit标准化产品单元。它的核心特征是标准化只要两件商品在功能、品类、关键公共属性上是同一个东西不管颜色、容量、尺寸怎么变它们共享同一个SPU。我常拿汽车来打比方SPU相当于车型。丰田卡罗拉是一个车型它在官网展示的时候有排量、车身结构、底盘类型这些共性信息至于你买的是白色还是黑色、是1.2T还是1.8L那是另一层的事情。放到电商场景里一个SPU通常包含这些信息商品名称比如iPhone 15 Pro Max类目手机品牌Apple主图、详情页图文售后服务模板公共规格参数屏幕尺寸、电池容量、支持的网络制式这些信息有什么特点它们与具体的颜色、内存版本、套餐无关。用户在商品详情页看到的大部分介绍内容本质上都是SPU层面的内容。评价体系也很有意思很多平台的商品评价挂的是SPU维度因为iPhone 15 Pro Max的体验是一致的不会因为换一种颜色评价就完全不同。2.2 SKU库存保有单位才是真正被买走的那个东西SKU全称是Stock Keeping Unit库存保有单位。这个词最早来自传统零售业的库存管理到了电商里它被定义为最小可售存货单位。还是用汽车打比方SKU不是车型而是某一台具体配置的车白色车身、1.8L排量、CVT变速箱、舒适版这一台才能被开走。电商里SKU的意思完全一样iPhone 15 Pro Max可能衍生出3种颜色乘以3种内存共9个SKU每个SKU有自己独立的条码、价格、库存数量。SKU才是交易的真正载体。用户在商详页点加入购物车加的是一个SKU订单里记录的商品快照本质上也是SKU层的信息库存系统扣减的是某一个SKU的库存而不是整个SPU的库存。所以SKU上一般会挂这些字段颜色、容量、套餐等具体规格值独立价格允许和SPU的起售价不同独立库存数量独立条码常用于对接仓储系统独立图片和重量体积信息关系到运费计算在代码层面如果看到一张商品相关表凡是行记录可以精细到某一具体颜色某一天库存多了5件的那它一定是SKU维度不是SPU维度。2.3 ID不是与SPU/SKU并列的东西而是它们的身份标签标题把ID和SPU、SKU并列放在一起问这个问法很常见但ID和它们并不是同一个维度的概念。SPU和SKU回答的是商品分哪几层ID回答的却是每一层的数据到底怎么唯一识别。简单说ID是一个唯一编号。它可以是数据库主键可以是一个全局唯一标识符也可以是业务编码。关键是要理解ID有层级SPU ID唯一标识一个SPUSKU ID唯一标识一个SKU。它们之间不存在ID包含SKU或者SKU包含ID这种关系ID更像身份证号码一群人有共同的身份定义比如中国公民但每个人有自己的身份证号。在实际系统里ID承担这几类职责系统内部主键数据库表的主键常见是自增ID或者雪花ID业务查询唯一标识通过商品ID调详情接口、拉SKU库存、做价格变更对外的平台标识比如对接菜鸟仓需要传SKU ID投放广告需要传商品ID跨系统关联键ERP、OMS、WMS、TMS之间通过ID完成数据流转把ID单独拎出来强调还有一个原因很多人做系统设计的时候只想着反正有个主键就行忽略了ID的生成规则和业务语义。后面我会专门讲ID设计的坑这里先记住一句话——SPU和SKU是分层模型ID是给每一层做唯一标注的机制两者配合使用才有意义。3. 它们之间的区别和联系一张对照表加四层关系3.1 先看一张对照表很多文章喜欢用标题党的方式讲区别但真正整理清楚还是表格最直接。做一个典型的电商商品模型对照对比维度SPUSKUID全称Standard Product UnitStock Keeping UnitIdentifier中文含义标准化产品单元库存保有单位唯一标识层级商品大类层具体售卖层贯穿所有层典型例子iPhone 15 Pro MaxiPhone 15 Pro Max 256G 原色钛金属SPU ID: 100123 / SKU ID: 880001核心作用信息聚合、展示、评价归集价格、库存、订单履约唯一识别、关联、查询能不能直接买不能能视标识对象而定一个概念能对应几条记录一个SPU对应多条SKU一个SKU只能属于一个SPU每个实体各有一个不可重复这张表里最容易记混的点在于SPU不能直接买SKU能直接买。以后面试或者跟同事对齐的时候只要记住这一条层次基本就不会搞错。3.2 四层关系拆开讲第一层是包含关系。一个SPU下面挂一到多个SKU。最小的时候一个SPU只挂一个SKU比如某些无规格商品多的时候能挂几十个。SPU是父SKU是子这个方向不能搞反。第二层是从属关系。SKU的规格值组合决定了它归属于哪个SPU。反过来看SPU存在的价值是把那些除了具体售卖属性外都一样的SKU合并成一个商品入口避免商详页出现几十条重复的标题。第三层是职责关系。SPU管展示、管详情、管聚合SKU管价格、管库存、管交易。同样一条商品数据在导航分类页和搜索结果页使用SPU信息在购物车和订单里使用SKU信息。如果职责没分清楚就会出现我开头说的那种改价事故。第四层是ID的纽带关系。SPU ID和SKU ID在数据库里扮演外键和业务键把两层的关联显式表达出来。任何一条SKU数据都应该包含spu_id字段即便它自身有独立的sku_id。也就是说ID不是独立的第三层而是把SPU和SKU连接起来的那根线。4. 落到数据库里是怎么建的商品主数据的标准设计概念说清楚以后还得落在表结构上才能体现出价值。这一节给出一个常见的商品主数据设计重点解释为什么这么建。4.1 SPU表和SKU表分开建最核心的一条原则SPU信息放一张表SKU信息放另一张表通过spu_id关联千万不要塞进一张表。原因有两点冗余严重。如果把SPU公共属性复制到每条SKU上一个SPU挂20个SKU标题、主图、详情这些大字段就要存20份数据量大了存储开销和一致性问题都很头疼。扩展性差。后续如果新增一个公共属性得把历史上所有SKU行全部刷新一遍分开建表只需要改SPU表的字段。两张表的简化示例-- SPU表存商品公共属性 CREATE TABLE spu ( spu_id BIGINT PRIMARY KEY, spu_code VARCHAR(64) NOT NULL, -- 业务编码如 P100123 title VARCHAR(200) NOT NULL, category_id INT NOT NULL, brand_id INT NOT NULL, main_image VARCHAR(500), detail_json JSON, -- 详情页富文本或结构化素材 status TINYINT DEFAULT 1, created_time DATETIME, updated_time DATETIME ); -- SKU表存具体售卖单元的个性化信息 CREATE TABLE sku ( sku_id BIGINT PRIMARY KEY, sku_code VARCHAR(64) NOT NULL, spu_id BIGINT NOT NULL, -- 从属关系 title VARCHAR(200) NOT NULL, -- 完整标题含规格描述 spec_values JSON, -- {颜色:原色钛金属,内存:256G} price DECIMAL(10,2) NOT NULL, -- 销售单价 market_price DECIMAL(10,2), stock_qty INT NOT NULL DEFAULT 0, bar_code VARCHAR(128), image_url VARCHAR(500), status TINYINT DEFAULT 1, created_time DATETIME, updated_time DATETIME, KEY idx_spu_id (spu_id) );这里有一个实践中的细节不要只建一个外键就算完spu_id上一定要建普通索引MySQL里如果建了外键约束自动会有索引但如果为了性能选择不建外键索引要记得手动加上否则按SPU查所有SKU的SQL会全表扫描。4.2 规格值到底怎么存常见的规格属性有颜色、尺码、版本、套餐、内存容量等。这些值在页面展示时需要逐个列出在创建SKU时又需要做组合所以设计上要区分规格属性定义和规格值定义。简单做法是用三张表规格属性表定义SPU有哪些规格维度比如颜色、内存属性值表定义每个维度的可选值比如颜色维度有黑色、白色、原色钛金属SKU与值的关系通过JSON字段或者单独的关联表记录每个SKU在各维度上的取值对于中小型系统直接在SKU表里放一个spec_values JSON字段已经足够。好处是创建SKU时可以把组合结果整串写入查询时也方便直接返回给前端渲染。坏处是如果后续要做按规格过滤的复杂查询SQL会比较绕需要用到JSON函数性能不如关系表稳定。电商平台体量大了以后一般会拆成schema mapping表加宽表两套并存一套负责事务写入一套负责查询加速。多规格SKU的生成算法比较简单本质是多个维度可选值的笛卡尔积。比如颜色3种、内存3种组合数就是3乘以3等于9个SKU。方案设计的时候要注意一点不是所有组合都必须存在比如某些颜色和内存的组合在生产端就不生产所以生成时不能无条件全部创建最好支持白名单过滤。4.3 一张图式的数据流转这里是文字说明从创建商品到真正上架顺序大概是先有SPU基础信息再创建SPU下的规格属性然后根据组合生成SKU最后针对每个SKU填写价格和库存。系统里做商品复制的功能也要特别小心。很多后台会提供复制商品能力复制维度要区分好是复制SPU结构还是复制SKU列表。如果只复制SPU生成新SPU ID之后SKU必须重新生成新的SKU ID不能沿用旧SKU ID否则会污染历史订单和报表的关联关系。这个细节我在好几个项目里都见过翻车。5. 从创建商品到下单发货三者在业务链路里各管一段概念和表设计都清楚了再看实际业务流转过程会更容易理解为什么SPU和SKU必须分开。用户打开一个商品列表页列表卡片上显示的是SPU层的标题、主图、价格区间比如¥5999起。点击进入商详页看到了商品图文介绍这也是SPU层的东西所有颜色共用一个详细介绍页面。页面下方出现规格选择器用户选了原色钛金属和256G此时页面展示的¥9999和有货才开始对应SKU层的数据。加入购物车和提交订单时订单行记录的核心商品字段就是SKU ID。这里有个常见误解订单里到底存SPU ID还是SKU ID答案是至少存SKU ID最好SPU ID也冗余存一份。原因有两方面履约必须到SKU粒度。仓库拣货要按SKU找货如果只存SPU ID仓库不知道具体拣哪个颜色哪个容量。冗余存储SPU ID是为了减少关联查询。订单量大以后每查一次订单都要join商品表很重把SPU ID冗余进去能少一次join。但注意如果商品标题或价格发生过变化订单里还需要快照字段不能完全依赖冗余商品表里被后面改动过的数据。售后阶段同样要看SKU。退货时退回的库存要加到对应SKU上如果申请退的是SPU ID系统完全不知道是该把库存退回黑色款还是白色款。所以售后单的SKU粒度判断是电商系统里很基础又很严肃的校验点。从这套链路可以总结出一个核心规律SPU管用户看到什么SKU管用户买到什么。展示层数据挂SPU交易和库存层数据挂SKU两个维度通过spu_id-sku_id的归属关系串联。6. ID怎么生成才靠谱自增、雪花、UUID和平台映射的利弊最后专门说一下ID相关的实战问题。前面讲了ID是身份标签但用ID做身份标签这件事到了工程层面远没有想象中简单。6.1 自增ID最快最简单但别用错地方MySQL自增主键使用方便、索引紧凑、写入性能好看SQL执行计划也直观。但它有几个明显的适用边界一旦系统需要做水平分库分表自增ID不能保证全局唯一分表后的id会冲突。外部相对容易根据ID的间隔估算业务量有些公司会刻意避免这种信息泄露。跨环境合并数据比如测试环境导生产数据时自增ID会产生主键冲突。我的建议是单体应用、量级不大、无跨环境合并需求的内部后台表用自增主键没有任何问题但商品、订单这类会走向分库分表和外部交互的核心表最好一开始就用全局ID方案后面对接中台时能省掉大量迁移成本。6.2 UUID全局唯一但别傻傻拿它当主键UUID字符串在分布式环境里生成简单、不用中心化发号器听起来很省事。但它有两个让数据库难受的点长度长36个字符并且无序。无序插入到B树索引里会引起大量页分裂写入性能会明显下降。如果坚持用UUID常见做法是把它转成binary存储或者只取其中一段做业务标识数据库主键还是单独用自增或雪花ID。6.3 雪花ID兼顾全局唯一和趋势递增的常见选型雪花IDSnowflake现在几乎是电商中后台系统的默认选择。它由时间戳、机器ID、序列号拼接而成产生的是一个64位整数既能全局唯一又带上了一定的时间递增特性。落到MySQL里用BIGINT存比字符串ID省空间索引性能也不错。但雪花ID不是完全没有坑。我见过最多的问题是机器ID分配混乱。有的团队直接把机器IP后面几位当workerId部署环境一变就可能重复导致生成的ID在同一毫秒内碰撞。另一个坑是时钟回拨。如果服务器NTP时间向回拨同一个时间戳范围内序列号可能重复严重点的系统会有专门的发号服务做兜底。在中小团队没有自建发号服务的条件下最靠谱的方案其实是让数据库生成一段序列号作为机器ID来源再用代码里独立的ID生成组件配合缓存尽量降低碰撞概率。不要在服务里写死写代码。6.4 平台外部ID和内部ID的映射才是电商特有的坑很多电商公司不是只做自营商城还要把商品分发到多个销售渠道每个渠道有一套自己的外部商品ID。这时候内部ID和外部ID的关系必须要有一张映射表以内部SKU ID为主键外部渠道标识加对方商品ID为唯一索引。实际业务中最常见的情况是渠道侧不允许直接用对方ID做主键而是要求传你本系统的编码然后渠道再建立自己的ID关联。这里一定要在映射表上维护中间状态字段比如是否已同步、最后一次同步时间防止每次渠道对接都在为ID不存在还是没同步而扯皮。6.5 给ID定一套命名和映射规范最后分享一个实际项目里的做法无论内部系统还是外部对接所有ID字段命名必须带上前缀语义。商品层用product_idSPU层用spu_idSKU层用sku_id外部渠道则用out_product_id和out_sku_id。字段一旦出现id这种裸命名后期排查数据问题基本全靠猜。同时在代码里加一道校验逻辑SPU层的接口如果收到sku_id直接拒绝而不是忽略。我见过很多系统把这两个ID互相兼容导致前端传错也没有人感知最后库存和价格乱套了才回头查接口入参。严谨的入参校验不值多少钱但能省掉多少个通宵排查的夜晚这笔账怎么算都是划算的。我在实际项目中还有一个一直保留的习惯SKU表里加一个规格摘要文本字段就是简单拼出颜色_内存_套餐这种字符串。它的存在让运营在排查库存时完全不需要去解析JSON或关联规格表直接看值就能定位是哪一条记录。时间久了大家可能觉得它冗余但遇到线上问题所有人都会感谢这个字段这是我的一个真实体会分享给你参考。
阅读完成 · 觉得有帮助?
咨询建站