中转平台如何做用户用量统计、计费、流量报表数据库表设计
中转平台的用户用量统计、计费和流量报表,不能只靠一张大表硬撑。
更合理的做法是按粒度拆分成流水表、账单表和聚合报表表:流水表负责记录每次请求产生的真实用量,账单表负责按周期结算扣费,报表表负责快速展示趋势和排行。
下面按这个思路说明表结构设计和落地要点。
先确认你要统计哪些维度
动手建表前,先回答三个问题:
- 用户维度:按 API Key、按账号,还是按子用户分别统计?推荐保留
user_id和api_key_id两个字段,方便后续切换计费对象。 - 计量对象:是记录 Token 消耗、请求次数、带宽流量,还是三者都需要?中转平台通常至少需要记录 请求次数 和 流量/Token 消耗。
- 统计周期:按小时、按天出报表,还是按自然月出账单?字段里要带上统计周期标识,避免月底汇总时反复全表扫描。
核心表怎么拆分
推荐用四类表组成基础结构:
1. 用户账户表
存放用户基础信息、当前余额和计费状态。
字段包括 user_id、balance、status、created_at。
实时扣费时,余额变更必须配合事务或乐观锁,防止超扣。
2. 用量流水表
这是核心流水账,每条记录代表一次实际调用或一次用量上报。
建议表名 usage_log,大致结构如下:
CREATE TABLE usage_log (
id BIGINT UNSIGNED AUTO_INCREMENT PRIMARY KEY,
user_id VARCHAR(32) NOT NULL COMMENT '用户ID',
api_key_id VARCHAR(64) NOT NULL COMMENT '调用时使用的Key',
model_name VARCHAR(64) DEFAULT NULL COMMENT '模型/渠道标识',
request_count INT NOT NULL DEFAULT 1 COMMENT '调用次数',
token_in INT NOT NULL DEFAULT 0 COMMENT '输入Token',
token_out INT NOT NULL DEFAULT 0 COMMENT '输出Token',
traffic_bytes BIGINT NOT NULL DEFAULT 0 COMMENT '流量,单位字节',
cost_amount DECIMAL(12,4) NOT NULL DEFAULT 0 COMMENT '预估费用',
request_time DATETIME NOT NULL COMMENT '请求发生时间',
INDEX idx_user_time (user_id, request_time),
INDEX idx_key_time (api_key_id, request_time)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;
流水表只做插入和查询,不要在这里做 UPDATE 累计。
如果同一批请求需要聚合,可以用 request_id 做去重,防止重复计费。
3. 计费账单表
账单表记录每个用户在某一个结算周期内的最终费用。
比如按小时出账、按天出账、按自然月出账。
关键字段是 bill_no、user_id、period_start、period_end、total_amount、deduct_status。
每次生成账单后,用事务同时更新用户账户余额,并记录扣费流水。
4. 报表聚合表
为了加快报表页面展示,可以按天或按小时把数据提前聚合。
聚合结果写入 report_daily 表,字段建议为 stat_date、user_id、model_name、total_calls、total_tokens、total_traffic、total_cost。
查询趋势图和用户排行时,直接从这个表做 GROUP BY,性能远好于扫 usage_log。
容易踩的三个坑
流量单位不统一。
有的地方按字节存储,有的地方按 MB 上报,报表出来的数字会离奇变大或变小。
建议数据库统一用 BIGINT 存字节,页面展示时再换算成 KB、MB、GB。
时区错位导致日账单不准确。
如果服务器是 UTC,用户在国内按北京时间看报表,就应让 stat_date 的时间语义由代码层固定为东八区,建表时在应用层算出日期后写入,不直接依赖 MySQL 的 NOW() 做跨时区转换。
扣费状态和账单状态脱节。
如果先扣费再生成账单,中途失败会导致账实不符。
正确顺序是:生成账单(状态为未扣费)→ 事务内扣费 → 更新账单状态 → 记录扣费流水。
任何时候出现异常,都能通过账单状态定位断点。
如何验证表设计是否合理
先用模拟数据连起来跑一遍:插入 3 个用户的用量流水,分别执行“按天汇总用户用量”和“按自然月生成账单”的 SQL,确认结果与手工计算一致。
再检查是否满足以下条件:
- 单个查询能通过
user_id + time索引快速圈定范围; - 报表聚合表数据与流水表手动统计结果一致;
- 对同一用户并发扣费时,余额不会扣成负数;
- 每分钟都在写入流水时,库存查询仍能保证秒级。
如果你的平台后续要接入多个上游渠道,建议在流水表额外增加 channel_id 字段。
这样可以按渠道分析成本,也能在渠道对账时快速定位每一笔费用的来源。
实际落地时不要追求一步到位,先按这个基础模型上线,再根据你的业务场景增加“折扣、套餐、退款”等字段即可。