3. 账户模型核心概念

在银行核心系统中,账户模型不是一张简单的数据表,而是承载资金归属、业务规则、风险控制与监管报送的基础结构。理解账户模型,必须抓住五个核心概念:账户标识解决“这是谁”,账户类型解决“这是什么”,余额结构解决“钱怎么放”,状态机解决“现在能不能动”,生命周期解决“从开到销怎么走”。这五者相互约束,共同构成账户的完整语义。

一、账户标识:唯一性与可追溯性

账户标识是账户在系统中的“身份证”。它通常不是单一字段,而是一组标识的组合:内部主键、账号、客户号、产品码、币种、开户机构、账户序号等。内部主键用于系统内唯一寻址,账号用于对外展示与交易,客户号用于关联客户视图,产品码与币种则决定账户的业务规则。

设计时要区分物理唯一与业务唯一。物理唯一由数据库主键或分布式ID保证;业务唯一则由“客户号+产品码+币种+机构”等维度组合约束。若只依赖账号,容易在换卡、升位、合并时产生断裂;若只依赖内部ID,又无法满足监管与对账的对外可读性。

建议在账户表中同时保留 account_id(内部主键)、account_no(对外账号)、customer_id、product_code、currency、branch_code,并为业务唯一性建立组合唯一索引。

二、账户类型:决定规则边界

账户类型不是简单的分类标签,而是规则路由的入口。常见维度包括:按客户分为个人账户与对公账户;按用途分为储蓄、结算、贷款、保证金、内部户;按币种分为本币与外币;按是否计息分为计息与不计息;按是否允许透支分为借记与信用。子账户模型下,主账户与子账户之间还存在“归集关系”“额度共享关系”“利息分摊关系”。

类型一旦确定,后续的余额校验、交易权限、计息方式、报表口径都会随之绑定。因此,账户类型应尽量正交化:用多个独立维度描述,而不是把所有组合塞进一个枚举值。否则每新增一个业务场景,就要修改代码分支。

三、余额结构:从账面到可用

余额结构是账户模型中最容易被低估的部分。一个账户的“钱”往往不是单一数字,而是多层余额的组合。典型结构包括:

  • 账面余额:会计意义上的余额,用于总账核对。
  • 可用余额:客户当前可动用的金额,等于账面余额减去冻结、质押、在途等。
  • 冻结余额:因司法、预授权、保证金等原因被锁定的部分。
  • 透支额度:允许超出账面余额使用的授信部分。
  • 子账户余额:在主账户下按用途、期限、利率拆分的明细余额。

设计余额结构时,必须明确“余额是实时计算还是持久化”。实时计算一致性好但性能压力大;持久化余额性能好但需要处理并发与对账。常见做法是:主余额持久化,辅助余额(如可用、冻结)通过明细汇总或缓存计算,并设置日终重算机制。

余额字段 含义 是否持久化 典型用途
ledger_balance 账面余额 是 总账、报表
available_balance 可用余额 可缓存 交易校验
frozen_balance 冻结余额 是 司法、预授权
overdraft_limit 透支额度 是 授信控制
sub_balance 子账户余额 是 利息分摊、专款专用

四、状态机与生命周期:从开户到销户

状态机描述账户在某一时刻的“可操作性”。常见状态包括:待激活、正常、冻结、止付、销户、睡眠。状态迁移必须由明确事件触发,例如开户成功进入正常,司法冻结进入冻结,解冻回到正常,销户申请进入销户中,最终销户。每个状态都要定义允许与禁止的交易类型。

生命周期则是状态机在时间轴上的展开:开户、激活、交易、计息、对账、冻结/解冻、销户、归档。生命周期管理的关键是事件驱动与不可逆点。例如销户是不可逆的,必须确保余额为零、无未结清利息、无挂起交易。子账户的生命周期通常依附于主账户,但可以有独立的冻结与关闭。

账户状态机与生命周期主线 待激活 正常 冻结/止付 销户 激活 冻结 解冻 销户 生命周期事件轴 开户 激活 交易/计息 对账/冻结 销户/归档

五、案例与误区复盘

某银行在子账户模型中曾出现“可用余额大于账面余额”的异常。排查发现:冻结余额在子账户维度扣减,但可用余额在主账户维度汇总,两个维度的更新不在同一事务中,导致并发下出现短暂不一致。修正方案是:以主账户为余额校验入口,子账户只记录明细与分摊,所有可用余额变更走统一余额服务,并引入版本号做乐观锁。

常见误区:把账户类型当成唯一枚举、把余额当成单一字段、把状态机当成可有可无的标记、把生命周期等同于开户与销户两个动作。这些都会在后续扩展子账户、多币种、多机构时集中爆发。

复盘要点:第一,标识要分层,内部与外部解耦;第二,类型要正交,规则可组合;第三,余额要分层,账面、可用、冻结、额度各司其职;第四,状态机要显式,每个状态定义允许交易;第五,生命周期要事件驱动,不可逆点必须前置校验。把这五点落到表结构与服务接口上,账户模型才具备可演进性。

-- 账户核心表结构示意
CREATE TABLE account (
  account_id      BIGINT PRIMARY KEY,
  account_no      VARCHAR(32) NOT NULL,
  customer_id     BIGINT NOT NULL,
  product_code    VARCHAR(16) NOT NULL,
  currency        CHAR(3) NOT NULL,
  branch_code     VARCHAR(16) NOT NULL,
  account_type    VARCHAR(16) NOT NULL,
  status          VARCHAR(16) NOT NULL,
  ledger_balance  DECIMAL(18,2) NOT NULL DEFAULT 0,
  frozen_balance  DECIMAL(18,2) NOT NULL DEFAULT 0,
  overdraft_limit DECIMAL(18,2) NOT NULL DEFAULT 0,
  opened_at       TIMESTAMP NOT NULL,
  closed_at       TIMESTAMP,
  version         INT NOT NULL DEFAULT 0
);