面试官问:电商项目的商品表怎么设计?SPU和SKU到底怎么分? 写在前面这是我之前面试时被问到的一个真实问题“你的线上超市项目里商品表是怎么设计的”数据库表的设计是我们日常开发中经常涉及的内容也是被面试官问的高频面试题。但很多候选人的回答往往只停留在“建几张表”的理论层面。一旦面试官追问“如果是社区超市这种非标品多的场景呢如果要兼顾线下拣货呢”往往就卡壳了。本文结合我接过的一个真实项目——「新万悦社区超市」从真实的业务痛点出发拆解我是如何用category、goods、goods_sku三张核心表支撑起复杂的商品管理后台与 C 端展示的。01. 为什么不能“一物一码”在设计“新万悦”的商品模块时我面临的最大挑战是业务的复杂性。社区超市不同于标准电商如卖手机。我们的商品既有标准的“可口可乐”也有非标的“现杀鲈鱼”既有线上的浏览下单也有线下的货架拣货。如果照搬简单的“一物一码”思维把“300g菠菜”和“500g菠菜”当成两个完全独立的商品建表后期在做价格调整和库存盘点时就会非常痛苦。经过反复推敲我最终确定了以SPU标准化产品单元 SKU库存量单位为核心的三层架构。02. 核心架构SPU定骨架SKU管血肉在电商系统中最忌讳的就是把所有属性都塞进一张大宽表。在我的设计中我采用了经典的双层架构来解耦这个问题。结合下面的后台编辑页面来看SPU (Standard Product Unit)商品的“骨架”SPU 解决的是“这是什么商品”的问题。看原型图的上半部分无论是“有机菠菜”还是“现杀鲈鱼”它们的商品名称、副标题、封面图、品牌、产地这些都是所有规格共有的属性。这些信息我只存一份在goods表中。这样做的好处是当我想修改商品的详情页介绍时只需要改一次所有规格都会同步更新保证了数据的一致性。SKU (Stock Keeping Unit)商品的“血肉”SKU 解决的是“具体卖哪个规格”的问题。请看原型图的下半部分表格。同一个 SPU 下衍生出了不同的规格规格差异有的按重量卖300g有的按家庭装卖2kg。价格差异300g 卖 12.8 元2kg 卖 66.00 元。库存独立300g 的可能还剩 100 份但 2kg 的可能已经卖光了。这些差异化的数据全部下沉到goods_sku表中管理。每一行 SKU 记录都通过goods_id关联回 SPU。03. 亮点一拒绝硬编码用 JSON 拥抱“多变”的规格面试官追问我“超市的规格千奇百怪有的按重量有的按个数你的 SKU 表怎么设计才能适应这种变化”很多初学者的回答是建spec_name和spec_value关联表。这种“三范式”设计在标准品电商里没问题但在社区超市这种业务场景下简直就是灾难。因为超市的商品规格太“野”了蔬菜按重量分饮料按包装分海鲜甚至可能按大小分。如果你用传统的关联表设计每增加一种新的规格维度你就得改代码、加字典表非常僵化。我的回答是spec_json字段我在goods_sku表中直接设计了一个spec_json字段varchar 类型用来存储规格的键值对。比如300g菠菜存的是{weight: 300g}2KG家庭装存的是{weight: 2kg, pack: 家庭装}这样做的好处是前端渲染极其灵活。前端拿到这个 JSON可以直接循环渲染出规格选择器。不管你是“重量”还是“包装”对前端来说只是一个 Key-Value 的展示完全不用改接口。以后超市要卖“按个卖”的西瓜数据库表结构一行代码都不用改。04. 亮点二面向线下的“实战级”字段预留面试官追问“你的项目是线上超市那怎么考虑线下履约的效率”做线上超市项目不能只盯着屏幕还要盯着仓库。我在设计表结构时特意预留了两个“不起眼”但至关重要的字段location_code货架号和barcode条形码。这两个字段是为了解决拣货效率问题的。当仓管人员拿着 PDA 去仓库拣货时系统会生成一张拣货单。如果 SKU 表里没有location_code拣货员就得满仓库找货。有了这个字段系统可以直接告诉拣货员“去 A-01-03 货架拿 2 份 300g 菠菜”。为了配合这个业务我在goods_sku表上专门建立了idx_location_code索引。当仓管人员按货架号批量导出要拣货的商品时这个索引就能保证查询速度飞快不会卡死数据库。这就是我在做项目时坚持的原则技术是为业务服务的不要为了所谓的“范式”而牺牲了业务的灵活性。05. 亮点三无限级分类与 C 端体验最后简单提一下分类设计。超市的分类通常很深“生鲜 - 水果 - 热带水果 - 芒果”。我在category表中使用了经典的parent_id自关联设计配合sort排序字段完美支撑了后台的拖拽排序和 C 端的侧边栏导航。如上图所示C 端左侧的一级分类导航右侧的二级分类筛选数据完全源自后台的树形结构。这种设计虽然传统但在数据量可控几千个分类的情况下是维护成本最低、扩展性最好的方案。06.核心表结构设计CREATE TABLE category ( id int NOT NULL AUTO_INCREMENT COMMENT 分类ID(自增即可,数据量小), parent_id int DEFAULT 0 COMMENT 父分类ID, 0为一级, name varchar(50) NOT NULL COMMENT 分类名称, icon_url varchar(255) DEFAULT NULL COMMENT 分类图标, sort int DEFAULT 0 COMMENT 排序值(越小越靠前), status tinyint DEFAULT 1 COMMENT 状态: 1显示 0隐藏, create_by varchar(50) DEFAULT system COMMENT 创建者, update_by varchar(50) DEFAULT system COMMENT 更新者, create_time datetime DEFAULT CURRENT_TIMESTAMP COMMENT 创建时间, update_time datetime DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT 更新时间, deleted tinyint DEFAULT 0 COMMENT 逻辑删除: 0正常 1删除, version int DEFAULT 0 COMMENT 乐观锁版本号, PRIMARY KEY (id), KEY idx_parent_id (parent_id) ) ENGINEInnoDB AUTO_INCREMENT2014 DEFAULT CHARSETutf8mb4 COLLATEutf8mb4_0900_ai_ci COMMENT商品分类表; CREATE TABLE goods ( id bigint NOT NULL COMMENT 商品ID(雪花算法), category_id int NOT NULL COMMENT 分类ID, name varchar(100) NOT NULL COMMENT 商品名称, sub_title varchar(200) DEFAULT NULL COMMENT 副标题/卖点, cover_image varchar(255) NOT NULL COMMENT 封面图, detail_images text COMMENT 详情图(JSON数组), brand varchar(50) DEFAULT NULL COMMENT 品牌, origin varchar(50) DEFAULT NULL COMMENT 产地, unit varchar(20) DEFAULT 件 COMMENT 单位: 件/斤/箱, is_weighing tinyint DEFAULT 0 COMMENT 是否称重品: 0否 1是, min_stock_alert int DEFAULT 10 COMMENT 库存预警阈值(SPU级默认, SKU可在inventory覆盖), status tinyint DEFAULT 1 COMMENT 状态: 1上架 0下架, is_recommend tinyint DEFAULT 0 COMMENT 是否推荐: 0否 1是, sales_count int DEFAULT 0 COMMENT 累计销量(XXL-Job定时异步刷入), create_by varchar(50) DEFAULT system COMMENT 创建者, update_by varchar(50) DEFAULT system COMMENT 更新者, create_time datetime DEFAULT CURRENT_TIMESTAMP COMMENT 创建时间, update_time datetime DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT 更新时间, deleted tinyint DEFAULT 0 COMMENT 逻辑删除: 0正常 1删除, version int DEFAULT 0 COMMENT 乐观锁版本号, PRIMARY KEY (id), KEY idx_category_status (category_id,status), KEY idx_recommend (is_recommend,status) ) ENGINEInnoDB DEFAULT CHARSETutf8mb4 COLLATEutf8mb4_0900_ai_ci COMMENT商品SPU表; CREATE TABLE goods_sku ( id bigint NOT NULL COMMENT SKU ID, goods_id bigint NOT NULL COMMENT 商品SPU ID, spec_json varchar(500) DEFAULT NULL COMMENT 规格JSON: {weight:500g}, price decimal(10,2) NOT NULL COMMENT 售价, original_price decimal(10,2) DEFAULT NULL COMMENT 划线价, barcode varchar(64) DEFAULT NULL COMMENT 商品条码(标品扫码拣货用, P03), location_code varchar(32) DEFAULT NULL COMMENT 货架库位(如 A-03-02, 拣货路径用, P02), weight_grams int DEFAULT NULL COMMENT 预估重量(克, 称重品下单时按此预估计价), status tinyint DEFAULT 1 COMMENT 状态: 1启用 0禁用, create_by varchar(50) DEFAULT system COMMENT 创建者, update_by varchar(50) DEFAULT system COMMENT 更新者, create_time datetime DEFAULT CURRENT_TIMESTAMP COMMENT 创建时间, update_time datetime DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT 更新时间, deleted tinyint DEFAULT 0 COMMENT 逻辑删除: 0正常 1删除, version int DEFAULT 0 COMMENT 乐观锁版本号(守商品属性并发编辑), PRIMARY KEY (id), UNIQUE KEY uk_barcode (barcode), KEY idx_goods_id (goods_id), KEY idx_location_code (location_code) ) ENGINEInnoDB DEFAULT CHARSETutf8mb4 COLLATEutf8mb4_0900_ai_ci COMMENT商品SKU表;总结那这道面试题如何回答呢如果面试时被问到“商品表怎么设计”建议按照以下逻辑回答能体现出你的业务思考深度先讲通用模型首先明确回答采用SPU SKU的双层模型。解释 SPU 存储公共属性名称、详情SKU 存储销售属性价格、库存、规格通过goods_id关联。再讲灵活扩展主动提到规格存储的难点。说明为了避免频繁改表采用了 JSON 字段如 spec_json来存储多变的规格键值对这样能极大提升系统的扩展性适应非标品业务。最后讲业务结合这是加分项。结合你的项目特点提到为了支持线下拣货在 SKU 表中预留了 location_code库位和 barcode条码字段并建立了相应索引。这能证明你不仅懂数据库设计还懂真实的业务流程。补充分类设计简单提及使用 parent_id实现无限级分类满足复杂的目录导航需求。下一篇我会继续拆解这个项目中的商品详情页的缓存设计方案我们一起进步