4. 库存核心表结构:商品表、仓库表、库存表、库存流水表的设计

好,咱们直接进入正题。库存系统最核心的,就是这四张表。我见过不少新手,一上来就想着怎么把库存算得特别准,结果表结构设计得一塌糊涂,后面改都改不动。说白了,表结构就是地基,地基歪了,楼盖得再高也得塌。

4.1 商品表(product)—— 一切业务的起点

商品表,记录的是「什么东西」。注意,它不记录数量,只记录属性。我在项目中遇到过有人把库存数量直接塞到商品表里,结果每次并发扣减都锁行,性能惨不忍睹。嗯,这是个大坑。

商品表的核心字段,我习惯这样设计:

CREATE TABLE `product` (
  `id` bigint(20) NOT NULL AUTO_INCREMENT COMMENT '商品ID',
  `sku_code` varchar(64) NOT NULL COMMENT 'SKU编码,唯一',
  `product_name` varchar(200) NOT NULL COMMENT '商品名称',
  `category_id` bigint(20) DEFAULT NULL COMMENT '分类ID',
  `unit` varchar(20) DEFAULT '件' COMMENT '计量单位',
  `status` tinyint(4) DEFAULT '1' COMMENT '状态:1启用 0停用',
  `create_time` datetime DEFAULT CURRENT_TIMESTAMP,
  `update_time` datetime DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
  PRIMARY KEY (`id`),
  UNIQUE KEY `uk_sku` (`sku_code`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='商品表';
我的经验:SKU编码一定要用唯一索引。曾经有个项目没加唯一约束,业务方导入数据时重复导入了两遍,结果库存对不上,查了整整两天。从那以后,我所有唯一性字段都强制加唯一索引。

4.2 仓库表(warehouse)—— 库存的物理位置

仓库表,记录的是「东西放在哪」。别小看这张表,多仓库场景下,它的设计直接决定了库存查询的复杂度。

我个人习惯给仓库表加一个「仓库类型」字段,区分实体仓和虚拟仓。为什么?因为有些场景下,比如预售商品,它其实没有物理库存,但你需要一个虚拟仓位来记录。

CREATE TABLE `warehouse` (
  `id` bigint(20) NOT NULL AUTO_INCREMENT COMMENT '仓库ID',
  `warehouse_code` varchar(64) NOT NULL COMMENT '仓库编码',
  `warehouse_name` varchar(200) NOT NULL COMMENT '仓库名称',
  `warehouse_type` tinyint(4) DEFAULT '1' COMMENT '类型:1实体仓 2虚拟仓',
  `address` varchar(500) DEFAULT NULL COMMENT '仓库地址',
  `status` tinyint(4) DEFAULT '1' COMMENT '状态',
  `create_time` datetime DEFAULT CURRENT_TIMESTAMP,
  PRIMARY KEY (`id`),
  UNIQUE KEY `uk_code` (`warehouse_code`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='仓库表';
注意:仓库编码不要用自增ID。你想想看,如果仓库编码是自增的,万一哪天要合并两个系统的数据,编码冲突就麻烦了。我建议用「WH」+日期+流水号这种有业务含义的编码。

4.3 库存表(inventory)—— 真正的核心

库存表,记录的是「某个商品在某个仓库有多少」。这是整个库存系统的核心,也是并发压力最大的地方。

库存表的设计,我踩过最大的坑就是「库存字段设计得太少」。一开始只设计了「可用库存」和「锁定库存」,结果后来业务方要求统计「在途库存」「次品库存」「预留库存」,改表改到想哭。

所以,我现在的建议是:

CREATE TABLE `inventory` (
  `id` bigint(20) NOT NULL AUTO_INCREMENT,
  `product_id` bigint(20) NOT NULL COMMENT '商品ID',
  `warehouse_id` bigint(20) NOT NULL COMMENT '仓库ID',
  `available_qty` int(11) DEFAULT '0' COMMENT '可用库存',
  `locked_qty` int(11) DEFAULT '0' COMMENT '锁定库存(已下单未出库)',
  `onway_qty` int(11) DEFAULT '0' COMMENT '在途库存',
  `defective_qty` int(11) DEFAULT '0' COMMENT '次品库存',
  `version` int(11) DEFAULT '0' COMMENT '乐观锁版本号',
  `create_time` datetime DEFAULT CURRENT_TIMESTAMP,
  `update_time` datetime DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
  PRIMARY KEY (`id`),
  UNIQUE KEY `uk_product_warehouse` (`product_id`, `warehouse_id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='库存表';
关键点:
  • 用「商品ID+仓库ID」作为联合唯一索引,保证一个商品在一个仓库只有一条记录
  • 加乐观锁版本号,用于高并发下的库存扣减
  • 库存字段全部用 int,不要用 decimal。为什么?因为库存是离散的整数,用 decimal 反而容易出精度问题

4.4 库存流水表(inventory_log)—— 审计的命脉

库存流水表,记录的是「库存为什么变了」。这张表平时看起来没啥用,但一旦出问题,它就是救命稻草。

我曾经遇到过一个线上事故:库存莫名其妙少了100件,业务方急得跳脚。幸好流水表设计得够详细,一查发现是某个定时任务重复执行了两次。如果没有流水表,这个锅可能就甩给数据库了。

CREATE TABLE `inventory_log` (
  `id` bigint(20) NOT NULL AUTO_INCREMENT,
  `product_id` bigint(20) NOT NULL COMMENT '商品ID',
  `warehouse_id` bigint(20) NOT NULL COMMENT '仓库ID',
  `change_type` tinyint(4) NOT NULL COMMENT '变动类型:1入库 2出库 3锁定 4解锁 5盘盈 6盘亏',
  `change_qty` int(11) NOT NULL COMMENT '变动数量(正数增加,负数减少)',
  `before_qty` int(11) NOT NULL COMMENT '变动前可用库存',
  `after_qty` int(11) NOT NULL COMMENT '变动后可用库存',
  `biz_no` varchar(100) DEFAULT NULL COMMENT '业务单号(如订单号、采购单号)',
  `remark` varchar(500) DEFAULT NULL COMMENT '备注',
  `operator` varchar(50) DEFAULT NULL COMMENT '操作人',
  `create_time` datetime DEFAULT CURRENT_TIMESTAMP,
  PRIMARY KEY (`id`),
  KEY `idx_product_warehouse` (`product_id`, `warehouse_id`),
  KEY `idx_biz_no` (`biz_no`),
  KEY `idx_create_time` (`create_time`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='库存流水表';
设计要点:
  • 记录变动前后的库存值,方便对账
  • 关联业务单号,出了问题能追溯到源头
  • 流水表只增不删,用时间索引来查询

4.5 四张表的关系与核心流程

这四张表的关系,我用一张图来展示:

商品表 product 仓库表 warehouse 库存表 inventory(核心) 库存流水表 inventory_log 1:N 1:N 1:N 核心逻辑:商品 + 仓库 → 库存 → 流水记录每一次变化

这张图你看懂了吗?商品表和仓库表是「主数据」,库存表是它们俩的「交集」,而流水表则是库存表的「操作日志」。每次库存变动,必须先写流水,再更新库存。这个顺序不能乱,否则一旦出问题,你连回滚的依据都没有。

4.6 避坑指南

我曾经踩过的坑,你千万别再踩:
  1. 库存字段用 decimal 而不是 int —— 库存是离散的,不是连续的。用 decimal 不仅浪费空间,还容易在计算时出现精度问题。
  2. 流水表没有关联业务单号 —— 出了问题你只能看到库存变了,但不知道是谁变的。查起来就像大海捞针。
  3. 没有乐观锁版本号 —— 高并发下库存扣减会出现超卖。我见过一个电商项目,双十一当天因为没有乐观锁,超卖了3000单,赔了十几万。
  4. 商品表和库存表混在一起 —— 这是最基础的设计错误。商品属性(名称、分类)和库存数量是两回事,分开才能灵活扩展。

好了,这四张表的设计思路就这些。记住一句话:库存系统的核心不是「算得准」,而是「每一笔变动都有据可查」。把流水表设计好了,你就成功了一半。


专注资料整理