如何设计数据库业务状态机
status 不是展示标签,而是控制哪些动作允许发生。
这页只讲一件事:已作废账目为什么不能继续进月报和余额。
| 事故 | 根因 | 设计动作 |
| 已作废账目还进月报。 | 只改主表状态,派生规则没跟上。 | 状态迁移必须带副作用清单。 |
| 管理员把 posted 改回 draft。 | 状态写路径散落。 | 用迁移规则和前置条件收口。 |
| 银行 reversed,本地仍 posted。 | 外部事实和本地主状态混在一起。 | 主状态、外部状态、展示状态分开。 |
| 已对账账目被原地改金额。 | 没区分活动态和锁定态。 | 锁定态只能调整或冲正。 |
| 状态修复后搜索仍错。 | 修状态没触发派生重算。 | 状态事件驱动重算和复核。 |
判断:状态机设计的核心不是列枚举,而是定义合法迁移、禁止绕写、记录原因、同步派生结果。
| 问题 | 最低设计 |
| 这是主业务状态,还是外部事实 / 展示状态 / 处理状态? | 不同含义不要混成一个 status。 |
| 哪些状态能进入余额和月报? | 统一派生规则,不让每条 SQL 自己猜。 |
| 状态能从哪里迁到哪里? | 迁移规则和前置条件。 |
| 谁推动了状态变化,为什么? | state_events 记录原因和操作者。 |
| 状态变化影响哪些派生结果? | 余额、月报、搜索、导出要有重算边界。 |
ledger_entries:
id, ledger_id, status, external_status
version, status_updated_at
state_events:
id, entity_type, entity_id
from_status, to_status, reason_code
actor_type, actor_id, request_id, created_at
state_transition_rules:
entity_type, from_status, to_status
required_condition, side_effect_policy
| 样本 | 通过标准 |
| posted 直接回 draft。 | 非法迁移被拦住。 |
| voided 仍进月报。 | 月报和余额对账失败并报警。 |
| reconciled 原地改金额。 | 禁止修改,要求调整分录。 |
| 外部回调重复到达。 | 状态迁移幂等,不重复触发副作用。 |
| 两个 worker 同时推进。 | 只有一个成功,另一个看到版本冲突。 |
记住:状态是控制面。没有迁移规则和状态事件,status 很快会变成谁都能改的危险字段。