深入浅出:电商系统中的 SPU 与 SKU 概念解析

前言

在电商系统开发中,SPUSKU 是两个最基础也最常被混淆的概念。无论你是产品经理、后端开发还是前端同学,搞清楚这两个概念对理解商品体系至关重要。

本文将从概念、关系、数据建模到实战场景,帮你一次性吃透 SPU 和 SKU。


一、概念定义

1.1 什么是 SPU?

SPU = Standard Product Unit(标准化产品单元)

SPU 指的是一个商品的抽象品类,是独立于具体规格的「商品主体」。它的核心特征是:属性基本相同的商品集合

举例:

SPU 说明
iPhone 16 Pro Max 不论颜色、存储容量,都属同一 SPU
Nike Air Force 1 经典款 不论尺码、颜色,都属同一 SPU
《三体》刘慈欣著 不论精装/平装,都属同一 SPU

一句话:SPU = “这东西是什么”


1.2 什么是 SKU?

SKU = Stock Keeping Unit(库存保有单位)

SKU 指的是一个具体可售卖的商品变体,是库存管理的最小粒度。每个 SKU 对应唯一的规格组合,拥有独立的库存和价格。

举例:

以「iPhone 16 Pro Max」这个 SPU 为例,它的 SKU 就有:

SKU 规格组合 库存
AP-IP16PM-BK-256 黑色 + 256G 200 台
AP-IP16PM-WT-256 白色 + 256G 150 台
AP-IP16PM-BK-512 黑色 + 512G 80 台
AP-IP16PM-WT-512 白色 + 512G 60 台

一句话:SKU = “具体买的是哪个”


二、核心区别一览

维度 SPU SKU
粒度 粗(品类层) 细(单品层)
唯一标识 SPU ID SKU ID
库存管理 无库存 有独立库存
价格 价格区间/参考价 具体售价
规格属性 共用属性(品牌、型号) 规格属性(颜色、尺寸)
图片 主图/详情图 规格图(可选)
典型场景 商品详情页 加购物车/下单

三、实体关系模型

一个 SPU 包含多个 SKU,这是一对多的关系。

┌──────────────────────────────────┐
│              SPU                 │
│  - spu_id: string (PK)           │
│  - name: string                  │
│  - brand: string                 │
│  - category_id: string           │
│  - description: text             │
│  - main_image: string            │
│  - created_at: datetime          │
└──────────────┬───────────────────┘
               │ 1
               │
               │ 包含
               │
               │ N
┌──────────────────────────────────┐
│              SKU                 │
│  - sku_id: string (PK)           │
│  - spu_id: string (FK)           │
│  - price: decimal                │
│  - stock: int                    │
│  - specs: JSON (规格组合)         │
│  - sku_image: string             │
│  - status: enum (上架/下架)      │
│  - created_at: datetime          │
└──────────────────────────────────┘

数据库表设计示例

-- SPU 表
CREATE TABLE spu (
    spu_id       VARCHAR(32) PRIMARY KEY,
    name         VARCHAR(200) NOT NULL,
    brand        VARCHAR(100),
    category_id  VARCHAR(32),
    description  TEXT,
    main_image   VARCHAR(500),
    status       TINYINT DEFAULT 1,
    created_at   DATETIME DEFAULT CURRENT_TIMESTAMP
);

-- SKU 表
CREATE TABLE sku (
    sku_id      VARCHAR(32) PRIMARY KEY,
    spu_id      VARCHAR(32) NOT NULL,
    price       DECIMAL(10,2) NOT NULL,
    stock       INT NOT NULL DEFAULT 0,
    specs       JSON NOT NULL,           -- {"颜色": "黑色", "存储": "256G"}
    sku_image   VARCHAR(500),
    status      TINYINT DEFAULT 1,
    created_at  DATETIME DEFAULT CURRENT_TIMESTAMP,
    FOREIGN KEY (spu_id) REFERENCES spu(spu_id)
);

-- 规格属性表(可选,用于管理规格模板)
CREATE TABLE spec_template (
    id          INT PRIMARY KEY AUTO_INCREMENT,
    spu_id      VARCHAR(32),
    spec_name   VARCHAR(50),             -- "颜色" / "尺寸" / "容量"
    spec_values JSON,                    -- ["黑色","白色","蓝色"]
    FOREIGN KEY (spu_id) REFERENCES spu(spu_id)
);

四、实战场景分析

场景 1:商品详情页

页面加载时:

  1. 查询 SPU:获取商品名称、描述、主图、品牌等基础信息
  2. 查询 SKU 列表:获取所有规格变体
  3. 前端组合规格:渲染规格选择器
// 前端伪代码
const spu = await getSpuById(spuId);           // SPU 基本信息
const skuList = await getSkuList(spuId);        // 所有 SKU

// 提取规格选项
const specOptions = extractSpecOptions(skuList);
// → { "颜色": ["黑色", "白色"], "存储": ["256G", "512G"] }

// 用户选择规格 → 匹配对应 SKU
function matchSku(color: string, storage: string): Sku {
  return skuList.find(sku =>
    sku.specs["颜色"] === color &&
    sku.specs["存储"] === storage
  );
}

场景 2:购物车与下单

购物车和订单只关心 SKU,不关心 SPU:

interface CartItem {
  skuId: string;       // 具体通哪个 SKU
  spuId: string;       // 关联的 SPU(用于展示商品名、图片)
  quantity: number;
  price: number;       // SKU 的当前价格
}

// 库存校验只针对 SKU
function checkStock(skuId: string, quantity: number): boolean {
  const sku = skuRepository.findById(skuId);
  return sku.stock >= quantity;
}

场景 3:促销活动

促销可以挂在 SPU 级别(全场通用),也可以挂在 SKU 级别(清特定库存):

interface Promotion {
  id: string;
  type: 'spu' | 'sku';           // 作用范围
  targetId: string;               // spu_id 或 sku_id
  discountType: 'percent' | 'fixed';
  discountValue: number;
}

// 计算价格
function calcPrice(sku: Sku, promotions: Promotion[]): number {
  let price = sku.price;

  // SPU 级促销(全场满减、折扣)
  const spuPromo = promotions.find(p => p.type === 'spu' && p.targetId === sku.spuId);
  if (spuPromo) price = applyDiscount(price, spuPromo);

  // SKU 级促销(清仓特价)
  const skuPromo = promotions.find(p => p.type === 'sku' && p.targetId === sku.skuId);
  if (skuPromo) price = applyDiscount(price, skuPromo);

  return price;
}

场景 4:库存预警

库存管理只出现在 SKU 层级:

-- 查询库存不足的 SKU
SELECT sku_id, specs, stock
FROM sku
WHERE stock < 10
AND status = 1
ORDER BY stock ASC;

-- 按 SPU 聚合查看库存情况
SELECT spu_id, COUNT(*) AS sku_count, SUM(stock) AS total_stock
FROM sku
GROUP BY spu_id
HAVING total_stock < 100;

五、前端交互设计要点

5.1 规格选择逻辑

┌─ 商品信息(SPU)─────────────────────┐
│ iPhone 16 Pro Max                     │
│ 价格: ¥7999 起                        │  ← 显示价格区间
│ 已选: 黑色 / 256G                     │
├───────────────────────────────────────┤
│ 颜色: ● 黑色  ● 白色  ○ 蓝色          │  ← 停售/缺货灰色不可选
│ 存储: [256G] [512G] [1TB]             │
├───────────────────────────────────────┤
│ 实付: ¥7,999.00                       │  ← 选中完整规格后显示具体价格
│ [加入购物车]                          │
└───────────────────────────────────────┘

5.2 高难度:SKU 矩阵联动

当有多个规格维度时,需要实现矩阵联动

规格:  颜色 × 存储 × 版本(国行/美版)
选中:  "黑色" + "256G" + "国行"
→ 唯一 SKU,显示价格和库存

联合禁用逻辑:
  选 "黑色" 后,存储选项中 "1TB" 可能无货 → 置灰
  再选 "国行" 后,可能只有 "256G" 有货 → 其余置灰

常见实现方案:

  • 笛卡尔积遍历匹配:小规格维度时可用
  • SKU Map 预计算:提前建立所有规格组合的映射表
  • GraphQL 按需查询:前端选择变化时动态查剩余库存

六、常见误区

误区 正确理解
SKU 就是商品条码 SKU 是内部管理编码,商品条码(UPC/EAN)是外部标准编码
一个商品只有一个 SKU 有规格的商品会有多个 SKU
SKU 和 SPU 可以互换 粒度不同,不可互换
所有商品都需要 SPU/SKU 分离 简单商品(无规格)可以 SPU = SKU

七、总结

层次 职责 谁关心
SPU 商品抽象、信息展示、归类 运营、前端展示
SKU 库存管理、价格计算、订单履约 仓储、财务、后端逻辑

记住这句口诀:

SPU 是「卖的是什么」,SKU 是「卖的是哪个」。

设计商品系统时,先从这两个概念入手,理清各自的职责边界,后续的扩展(多规格、多渠道库存、促销分摊等)就会顺畅很多。

Logo

电商企业物流数字化转型必备!快递鸟 API 接口,72 小时快速完成物流系统集成。全流程实战1V1指导,营造开放的API技术生态圈。

更多推荐