直接用JOIN无法正确实现FIFO库存计算,因为JOIN不保证顺序,无法按入库时间逐批扣减;FIFO需“逐批扣减、剩余归零、跳过耗尽批次”,而JOIN仅做匹配,不模拟状态流转过程。

为什么直接用 JOIN 无法正确实现 FIFO 库存计算
因为 JOIN 本身不带顺序保证,即使你 ORDER BY receipt_date,标准 SQL 的 JOIN 执行顺序由优化器决定,不会按入库时间逐条匹配出库记录。常见错误是写成:
SELECT * FROM inventory_in i<br>JOIN inventory_out o ON i.item_id = o.item_id<br>WHERE i.receipt_date <= o.out_date<br>ORDER BY i.receipt_date;结果会重复匹配、多对多爆炸,根本算不出剩余库存或单笔出库对应哪批货。
用窗口函数 + 自连接模拟 FIFO 消耗链
核心思路:对每笔入库记录,算出「它能覆盖哪些出库」,即从最早出库开始累加,直到累计出库量 ≥ 当前入库量。需要两个关键步骤:
- 给所有
inventory_in和inventory_out按时间排序并打上全局序号(ROW_NUMBER() OVER (PARTITION BY item_id ORDER BY date)) - 自连接时限制:只让第
i笔入库匹配第1到第j笔出库,且前j-1笔出库总量 in_qty,前j笔 ≥in_qty - 注意:必须用
item_id分组,否则跨商品串货;日期字段要统一为DATE类型,避免时间戳精度干扰排序
PostgreSQL/SQL Server 中可用的简化方案:递归 CTE
适用于中小规模数据(万级出入库记录),逻辑最贴近人工 FIFO 过程:
PostgreSQL 18.4 官方 Ubuntu 安装包现已发布,这是目前最新的稳定版本。推荐通过官方 APT 仓库安装:先执行 sudo apt update 更新索引,再运行 sudo apt install postgresql-18 即可完成部署。新版本引入了异步 I/O 子系统,在顺序扫描与 VACUUM 场景下性能提升显著,同时支持 UUID v7 原生生成函数与虚拟生成列。
WITH RECURSIVE fifo AS (<br> -- 初始:最早一笔未消耗的入库<br> SELECT id, item_id, qty AS remaining, receipt_date, 1 AS level<br> FROM inventory_in<br> WHERE id = (SELECT MIN(id) FROM inventory_in WHERE item_id = 'A001' AND qty > 0)<br> UNION ALL<br> -- 匹配最早可消耗的出库,并更新剩余量<br> SELECT f.id,<br> f.item_id,<br> GREATEST(0, f.remaining - o.qty) AS remaining,<br> f.receipt_date,<br> f.level + 1<br> FROM fifo f<br> JOIN inventory_out o ON f.item_id = o.item_id<br> AND o.out_date >= f.receipt_date<br> AND o.used = false<br> WHERE f.remaining > 0<br>)<br>SELECT * FROM fifo WHERE remaining = 0;
⚠️ 实际使用需配合 used 标记位或临时表避免重复匹配;MySQL 8.0+ 支持递归 CTE,但性能随深度下降明显。
生产环境更稳的选择:应用层分步处理 + 数据库辅助索引
数据库硬扛 FIFO 容易锁表、慢查询,尤其在高并发出入库场景。推荐组合策略:
- 入库时写入
inventory_in,并维护一张轻量级fifo_queue表,仅存(item_id, in_id, available_qty, position),按receipt_date索引 - 出库请求来时,用
SELECT ... FOR UPDATE SKIP LOCKED(PostgreSQL)或sp_getapplock(SQL Server)争抢队列头,原子扣减available_qty - 当某笔入库
available_qty降为 0,从队列中DELETE对应行 —— 这步极快,避免大表扫描 - 全量库存汇总走物化视图或定时任务,不实时依赖 FIFO 链路
FIFO 的本质是状态机,不是纯关系运算;强行用复杂 SQL 拼,容易在边界 case(如同秒多笔出入库、负库存冲正)里崩掉,不如把顺序控制权交给应用层。

















