生效时间和失效时间字段如何设计?如何查询当前有效数据?
简化版
有版本生效语义的数据通常设计 effective_start 和 effective_end,表示某条配置或价格在某个时间区间有效。查询当前有效数据时用 start <= now 且 end > now,并通过唯一约束或业务校验避免同一对象的有效期重叠。
详细版
价格、合同、费率、权益规则、配置策略都可能不是“立即覆盖”,而是从未来某个时间开始生效。如果只保留一条当前记录,就无法表达预约生效和历史追溯。
常见做法是每次变更新增一条版本记录,记录生效起止时间。effective_end 可以用一个远未来时间表示仍有效,也可以允许为空,但查询和索引要统一。
关键难点是防止时间区间重叠。例如同一个商品在 8 月 1 日到 8 月 10 日不能有两条不同价格同时有效。
完整版教学
一、为什么不能只覆盖当前值
很多业务数据有“什么时候开始算数”的问题。商品促销价下周一生效,会员权益下个月变更,合同费率按签约日期执行。
如果只在主表里保存一个当前值,每次修改都覆盖旧值,就会丢失历史,也无法预约未来变更。
生效时间设计的价值是把数据从“一个点”变成“一段时间内有效的版本”。
二、典型表结构怎么设计
可以为价格、规则、合同条款建版本表:
CREATE TABLE product_price_version (
id BIGINT PRIMARY KEY,
product_id BIGINT NOT NULL,
price DECIMAL(18,2) NOT NULL,
effective_start DATETIME NOT NULL,
effective_end DATETIME NOT NULL,
status VARCHAR(20) NOT NULL,
KEY idx_product_time (product_id, effective_start, effective_end)
);
查询当前有效价格:
SELECT *
FROM product_price_version
WHERE product_id = ?
AND effective_start <= NOW()
AND effective_end > NOW()
AND status = 'ACTIVE';
这里推荐左闭右开区间 [start, end),可以避免两个版本在边界点重复命中。
三、为什么要防止区间重叠
同一个对象同一时刻只能有一个有效版本。否则查询当前价格时可能返回两条,业务不知道用哪一条。
例如商品 1001 有两个版本:
版本A:2026-08-01 00:00 ~ 2026-08-10 00:00,99 元
版本B:2026-08-05 00:00 ~ 2026-08-15 00:00,89 元
8 月 6 日两条都有效,这就是重叠。多数数据库不能简单用普通唯一索引表达“区间不重叠”,需要应用校验、事务锁或数据库特定约束。
四、effective_end 用 NULL 还是远未来时间
NULL 表示无结束时间,语义清晰;远未来时间如 9999-12-31 查询更简单。两种都能用,关键是全系统统一。
| 方案 | 优点 | 缺点 |
|---|---|---|
end IS NULL | 语义自然 | 查询条件多一个分支 |
9999-12-31 | 范围查询统一 | 魔法值要规范 |
| 单独 current 标记 | 查询快 | 容易和时间区间不一致 |
如果使用 current_flag,也要把它当冗余索引字段维护,不能替代时间区间。
五、变更流程要可审计
有生效时间的数据通常涉及合同、价格、权益,不能随便 update。更稳的做法是新增版本、关闭旧版本、保留操作人和审批记录。
当未来版本还没生效时,允许撤销或修改;一旦已经生效并被订单引用,就要谨慎处理,通常通过新版本修正,而不是直接改历史版本。
这和快照设计配合使用:订单记录成交时刻的价格快照,价格版本表负责解释当时价格来源。
六、常见误区与追问
- 误区:生效时间只要一个 start 字段就够。 没有 end 很难表达历史区间和当前查询边界。
- 误区:用最大 id 就能代表当前版本。 最大 id 不一定已经生效,也不一定没被撤销。
- 误区:时间区间重叠无所谓。 重叠会导致同一时刻多条有效记录,业务语义冲突。
- 追问:为什么推荐左闭右开? 相邻版本
end = next_start时不会在边界重复命中。 - 追问:如何避免重叠? 用事务内范围检查、对象维度锁、排他约束或审批发布流程控制。
七、加强记忆
记忆钩子:生效时间设计不是记录“改过什么”,而是给规则画时间轴;任何时刻只能有一条线覆盖当前点。
回答这题要讲“版本表、左闭右开、当前查询、防重叠、历史审计”。它本质是时态数据建模,不是简单多两个时间字段。