订单状态须用tinyint或enum类型存储,发货操作需事务+行级锁,库存扣减须原子化,发货记录应独立建表shipsments以支持多次发货与物流追踪。

订单状态字段必须用 ENUM 或 TINYINT,别用 VARCHAR
用字符串存状态(比如 'pending'、'shipped')看着直观,但查起来慢、改起来难、还容易拼错。MySQL 里最稳妥的是 TINYINT:0=待支付,1=已支付,2=已发货,3=已完成,-1=已取消。或者用 ENUM('pending','paid','shipped','done','canceled')——它底层也转成整数,还能防非法值。
如果已有表用了 VARCHAR,别直接 ALTER TABLE ... MODIFY status VARCHAR(20) 然后手动 UPDATE,得先加约束再批量更新:
ALTER TABLE orders ADD COLUMN status_new TINYINT DEFAULT 0; UPDATE orders SET status_new = CASE status WHEN 'pending' THEN 0 WHEN 'paid' THEN 1 WHEN 'shipped' THEN 2 ELSE 0 END; ALTER TABLE orders DROP COLUMN status, CHANGE status_new status TINYINT NOT NULL;
发货操作必须用事务 + 行级锁,不能只靠应用层判断
常见错误是先 SELECT status FROM orders WHERE id = 123,发现是 paid 就执行 UPDATE ... SET status = 2。这在并发下会超发:两个发货员同时查到同一单是 paid,都去更新,结果发了两次货。
正确做法是在一条语句里完成校验和更新,并加 FOR UPDATE 锁住这行:
BEGIN; SELECT * FROM orders WHERE id = 123 AND status = 1 FOR UPDATE; -- 如果查不到,说明状态不对,直接 ROLLBACK UPDATE orders SET status = 2, shipped_at = NOW(), tracking_no = 'SF123456789' WHERE id = 123 AND status = 1; -- 检查 ROW_COUNT() 是否为 1,不是就说明被别人抢先改了 COMMIT;
发货时要同步更新库存,且库存扣减必须原子化
订单发货 ≠ 单纯改订单状态。真实场景中,发货前得确认对应商品的库存是否足够(尤其是预售或多仓场景),发货后要扣减可售库存。别在应用里先查库存再扣减——中间可能被其他订单抢走。
请注意以下说明:1、本程序允许任何人免费使用。2、本程序采用PHP+MYSQL架构编写。并且经过ZEND加密,所以运行环境需要有ZEND引擎支持。3、需要售后服务的,请与本作者联系,联系方式见下方。4、本程序还可以与您的网站想整合,可以实现用户在线服务功能,可以让客户管理自己的信息,可以查询自己的订单状况。以及返点信息等相关客户利益的信息。这个功能可提高客户的向心度。安装方法:1、解压本系统,放在
推荐在发货事务里一起处理,用子查询或 JOIN 确保一致性:
UPDATE products p JOIN order_items oi ON p.id = oi.product_id SET p.stock = p.stock - oi.quantity WHERE oi.order_id = 123 AND p.stock >= oi.quantity;
如果这条语句影响行为 0,说明某商品库存不足,整个发货事务该回滚。
- 库存字段建议用
INT(无符号),避免负数 - 高并发时考虑把库存拆到缓存(如 Redis)做预占,MySQL 只做最终落库
- 不要用
UPDATE ... SET stock = stock - 1而不校验,这是典型的“超卖”温床
发货记录要独立建表,别堆在 orders 里加一堆 shipped_at、carrier、tracking_no
一张订单可能多次发货(比如分批发货、补发、换货),如果所有字段都塞进 orders 表,历史轨迹就没了,查询也变重。应该拆出 shipments 表:
CREATE TABLE shipments ( id BIGINT PRIMARY KEY AUTO_INCREMENT, order_id BIGINT NOT NULL, status TINYINT DEFAULT 1, -- 1=created, 2=packed, 3=shipped, 4=delivered shipped_at DATETIME NULL, carrier VARCHAR(32), tracking_no VARCHAR(64), created_at DATETIME DEFAULT CURRENT_TIMESTAMP, INDEX idx_order_id (order_id), FOREIGN KEY (order_id) REFERENCES orders(id) ON DELETE CASCADE );
这样每次发货写一行,订单状态字段只反映「当前整体进展」,而 shipments 表承载完整操作日志。导出物流数据、对接快递平台、做发货时效分析,都靠这张表撑着。
真正麻烦的是状态流转逻辑——比如「已发货」不等于「物流已揽收」,系统得能区分业务状态和物流节点。这个边界一旦模糊,售后查件就会卡在「到底发没发」的扯皮里。









