快照表应该如何设计?订单为什么要保存商品快照?
简化版
快照表用于保存业务发生当时的关键数据,避免后续主数据变化影响历史记录。订单保存商品名称、价格、规格、图片等快照,是为了保证订单详情、售后、对账和审计都能还原交易发生时的事实。
详细版
如果订单只存 product_id,页面展示时再查商品表,那么商品改名、改价、换图后,历史订单会被“改写”。这对交易系统是错误的。
常见设计是在订单明细表里冗余一份商品快照字段,或者单独建订单商品快照表。快照字段包括商品标题、SKU 名称、成交单价、数量、营销信息、税率、商家信息等。
快照不是随意冗余。它保存的是“历史事实”,通常不跟随主表更新;如果确实要修正,只能走冲正、备注、审计记录,而不是静默覆盖。
完整版教学
一、为什么历史记录不能总查主表
主表代表当前状态,订单代表历史事实。商品表里的价格是现在的价格,订单里的价格是下单那一刻的成交价格,两者语义不同。
假设用户 10:00 以 99 元买了商品,12:00 商品涨到 129 元。如果订单详情实时查商品价格,就会显示用户买成了 129 元,售后和对账都会出错。
所以订单不能只保存外键,还要保存足够的交易快照。
二、快照字段应该放哪里
小型快照可以直接放在订单明细表。例如商品名、SKU 名、成交价、数量这类高频展示字段,放在 order_item 里查询最方便。
快照字段很多时,可以拆成独立快照表或 JSON 快照字段。拆表能避免订单明细过宽,JSON 适合保存低频展示的扩展信息。
CREATE TABLE order_item (
id BIGINT PRIMARY KEY,
order_id BIGINT NOT NULL,
product_id BIGINT NOT NULL,
sku_id BIGINT NOT NULL,
product_name VARCHAR(255) NOT NULL,
sku_name VARCHAR(255) NOT NULL,
deal_price DECIMAL(18,2) NOT NULL,
quantity INT NOT NULL
);
三、快照不是备份整张主表
快照要围绕业务场景选择字段,不是把商品表所有列复制一份。订单需要的是交易、展示、对账和售后必要字段。
例如商品详情页有 80 个字段,订单快照可能只需要 12 个。如果把所有字段都复制,会增加存储、写入和后续字段兼容成本。
| 字段类型 | 是否适合快照 | 原因 |
|---|---|---|
| 成交价 | 适合 | 对账核心事实 |
| 商品名 | 适合 | 历史展示需要 |
| 当前库存 | 不适合 | 会变化,非历史事实 |
| 商品详情长文 | 视情况 | 售后争议可能需要 |
四、快照如何处理变更和修正
快照一旦生成,原则上不随主表变化。商品改名、商家换 logo、类目调整,都不应该自动改历史订单。
如果确实发现快照写错,例如价格计算 bug,要通过补偿记录、调整单或审计日志修正,而不是直接 update 历史快照让证据消失。
这体现了一个数据库设计原则:历史事实可以被修正,但修正本身也要留下痕迹。
五、快照和冗余字段的区别
冗余字段常用于性能优化,可能会跟随主数据同步;快照字段用于保留历史语义,通常不跟随更新。
例如用户昵称冗余到评论表,可能希望昵称变化后评论展示也变化;但订单收货人信息是交易快照,不能因为用户改了默认地址就改变历史订单。
这个区别在面试中很关键,因为它决定了一致性策略:冗余要同步,快照要冻结。
六、常见误区与追问
- 误区:有外键就不需要快照。 外键只能找到当前主数据,不能保证历史事实不变。
- 误区:快照就是复制主表全部字段。 只保存业务还原需要的关键字段,避免无意义膨胀。
- 误区:主表改了快照也要同步。 快照的价值就是冻结当时事实,通常不跟随更新。
- 追问:订单商品快照至少保存什么? 商品名、SKU 名、成交价、数量、商家和必要营销信息。
- 追问:快照写错怎么办? 走修正单、补偿或审计记录,不能静默覆盖历史证据。
七、加强记忆
记忆钩子:主表是“现在的样子”,快照是“当时的照片”;订单要看照片,不能每次都让商品重新化妆再拍。
回答这题按“历史事实和当前状态分离 → 字段选择 → 冻结语义 → 修正留痕”展开。面试官考的不是冗余字段,而是你是否理解交易系统的历史一致性。