4. 预算占用与分摊的数据库表结构设计
预算占用与分摊的数据库表结构是整个预算控制体系的数据基石。设计不合理将直接导致占用记录丢失、分摊结果错误、对账不平以及性能瓶颈。本章围绕四张核心表——预算主表、预算明细表、占用流水表、分摊结果表——展开设计规范与关键字段说明。
4.1 四表关系总览
四张表构成“预算主体 → 预算明细 → 占用记录 → 分摊结果”的逐层细化链路。预算主表定义预算年度、版本与总额;预算明细表按科目/部门/项目拆分预算额度;占用流水表记录每笔业务对预算的实时锁定;分摊结果表则记录预算被最终消耗或调整后的归属明细。
设计原则: 占用流水表只增不删(逻辑删除),分摊结果表支持多次分摊与回滚,预算明细表与占用流水表通过预算明细ID建立外键约束。
4.2 预算主表(budget_master)
预算主表是预算体系的顶层容器,每条记录代表一个预算版本(如2025年度预算、2025年Q1滚动预算)。
| 字段名 | 类型 | 说明 |
|---|---|---|
| budget_id | BIGINT PK | 预算主键ID |
| budget_year | INT | 预算年度,如2025 |
| version_no | VARCHAR(20) | 版本号,如V1.0、V2.0 |
| total_amount | DECIMAL(18,2) | 预算总额 |
| status | TINYINT | 状态:0-草稿,1-已发布,2-冻结,3-已关闭 |
| created_at | DATETIME | 创建时间 |
设计要点: 预算主表应包含版本号字段,支持多版本并行与版本对比。状态字段用于控制预算是否允许被占用。
4.3 预算明细表(budget_detail)
预算明细表将主表的总额按科目、部门、项目、期间等维度拆解为可执行的控制单元。
| 字段名 | 类型 | 说明 |
|---|---|---|
| detail_id | BIGINT PK | 明细ID |
| budget_id | BIGINT FK | 关联预算主表 |
| account_code | VARCHAR(30) | 科目编码,如5001-办公费 |
| dept_code | VARCHAR(20) | 部门编码 |
| project_code | VARCHAR(30) | 项目编码(可选) |
| period_month | TINYINT | 预算月份,1-12 |
| budget_amount | DECIMAL(18,2) | 本明细预算额度 |
| used_amount | DECIMAL(18,2) | 已占用金额(冗余字段,用于快速校验) |
| frozen_amount | DECIMAL(18,2) | 冻结中金额(未最终确认的占用) |
避坑提示: used_amount 和 frozen_amount 是冗余字段,必须通过占用流水表实时汇总来保证一致性。建议通过定时任务或触发器定期校验,防止数据不一致导致预算超支或假性占用。
4.4 占用流水表(budget_occupy_flow)
占用流水表是预算控制的核心操作日志,每笔业务(如采购申请单、费用报销单)在占用预算时生成一条流水记录。
| 字段名 | 类型 | 说明 |
|---|---|---|
| flow_id | BIGINT PK | 流水ID |
| detail_id | BIGINT FK | 关联预算明细 |
| biz_type | VARCHAR(20) | 业务类型:PO-采购单,EXP-报销单,PR-预付款 |
| biz_no | VARCHAR(50) | 业务单据编号 |
| occupy_amount | DECIMAL(18,2) | 占用金额 |
| occupy_status | TINYINT | 状态:0-占用中,1-已释放,2-已确认消耗 |
| occupy_time | DATETIME | 占用时间 |
| release_time | DATETIME | 释放时间(释放或确认消耗时更新) |
| remark | VARCHAR(200) | 备注,如占用原因、关联发票号 |
关键设计: 占用流水表采用“只增不删”策略,释放或消耗时通过更新状态字段实现,保留完整审计轨迹。biz_type + biz_no 联合索引可快速定位某笔业务的所有占用记录。
4.5 分摊结果表(budget_allocation_result)
分摊结果表记录预算被最终消耗后的归属明细。当一笔业务涉及多个预算科目或部门时(如差旅费分摊到多个项目),分摊结果表记录每条分摊行的数据。
| 字段名 | 类型 | 说明 |
|---|---|---|
| allocation_id | BIGINT PK | 分摊ID |
| flow_id | BIGINT FK | 关联占用流水 |
| detail_id | BIGINT FK | 关联预算明细(目标明细) |
| allocation_amount | DECIMAL(18,2) | 分摊金额 |
| allocation_ratio | DECIMAL(5,4) | 分摊比例,如0.3333 |
| allocation_type | TINYINT | 分摊类型:0-自动分摊,1-手工分摊,2-回滚分摊 |
| allocation_time | DATETIME | 分摊时间 |
| batch_no | VARCHAR(30) | 分摊批次号,用于批量回滚 |
分摊场景示例: 一笔10,000元的差旅费报销单,按比例分摊到A项目(60%)和B项目(40%),则分摊结果表生成两条记录,分别关联不同的预算明细ID。batch_no 字段支持按批次一键回滚所有分摊记录。
4.6 核心SQL示例:占用与分摊写入
以下示例展示占用流水与分摊结果表的典型写入逻辑,采用事务保证数据一致性。
-- 预算占用写入(事务开始)
START TRANSACTION;
-- 1. 写入占用流水表
INSERT INTO budget_occupy_flow (detail_id, biz_type, biz_no, occupy_amount, occupy_status, occupy_time)
VALUES (1001, 'EXP', 'EXP20250301001', 5000.00, 0, NOW());
-- 2. 更新预算明细表的冻结金额
UPDATE budget_detail
SET frozen_amount = frozen_amount + 5000.00
WHERE detail_id = 1001;
COMMIT;
-- 分摊结果写入(确认消耗时)
START TRANSACTION;
-- 1. 写入分摊结果表
INSERT INTO budget_allocation_result (flow_id, detail_id, allocation_amount, allocation_ratio, allocation_type, allocation_time, batch_no)
VALUES (20001, 1001, 3000.00, 0.6000, 0, NOW(), 'BATCH20250301'),
(20001, 1002, 2000.00, 0.4000, 0, NOW(), 'BATCH20250301');
-- 2. 更新占用流水状态为已确认消耗
UPDATE budget_occupy_flow
SET occupy_status = 2, release_time = NOW()
WHERE flow_id = 20001;
-- 3. 更新预算明细表的已占用金额和冻结金额
UPDATE budget_detail
SET used_amount = used_amount + 3000.00,
frozen_amount = frozen_amount - 3000.00
WHERE detail_id = 1001;
UPDATE budget_detail
SET used_amount = used_amount + 2000.00,
frozen_amount = frozen_amount - 2000.00
WHERE detail_id = 1002;
COMMIT;
异常处理要点: 上述事务中任何一步失败都必须回滚全部操作。实际生产环境中建议使用分布式事务或补偿机制,防止因数据库宕机导致占用流水与明细表数据不一致。
4.7 表结构设计检查清单
- 索引设计: 预算明细表需建立 (budget_id, account_code, dept_code, period_month) 联合索引;占用流水表需建立 (detail_id, occupy_status) 索引。
- 数据归档: 占用流水表数据量增长快,建议按季度或年度进行分区表设计,历史数据迁移至归档库。
- 并发控制: 预算明细表的 frozen_amount 和 used_amount 更新必须使用行级锁(SELECT ... FOR UPDATE)防止并发超占。
- 审计字段: 所有表建议统一添加 create_by、update_by、create_time、update_time 四个审计字段,便于故障排查。
- 版本兼容: 预算主表的 version_no 字段支持多版本并行,分摊结果表通过 batch_no 支持回滚到指定版本。
4.8 常见设计缺陷与故障案例
| 缺陷 | 现象 | 解决方案 |
|---|---|---|
| 占用流水表缺少 biz_type 索引 | 按业务单据查询占用记录时全表扫描,接口超时 | 添加 (biz_type, biz_no) 联合索引 |
| 分摊结果表未设计 batch_no | 分摊错误后无法批量回滚,需逐条手工处理 | 增加 batch_no 字段,支持按批次回滚 |
| 预算明细表 used_amount 与流水表不一致 | 预算余额显示错误,导致超支或假性占用 | 增加定时对账任务,每日校验汇总数据 |
| 未使用行级锁更新 frozen_amount | 高并发下出现超占,预算被超额占用 | UPDATE 语句前加 SELECT ... FOR UPDATE |
总结: 预算占用与分摊的数据库设计核心在于“记录完整、状态可追溯、并发安全”。四张表各司其职,配合事务与索引设计,才能支撑企业预算控制的高频写入与实时校验需求。