如何统计每个用户token消耗,输入输出分开统计数据库设计
在日常开发大模型API中转或计费系统时,经常需要回答“每个用户到底消耗了多少Token”。
统计每个用户Token消耗,不能只记一个总量,否则无法分析输入输出占比。
正规做法是在数据库里用两个独立字段分别记录输入Token和输出Token,再按用户聚合。
下面这套数据库设计方案,适合零基础直接照做。
先确定统计口径和字段
在写SQL之前,先想清楚:每次API调用,模型会返回 prompt_tokens(输入部分)和 completion_tokens(输出部分),这两个值必须原样落库。
因此最少需要一张调用流水表,记录用户ID、请求ID、模型名称、时间、输入Token、输出Token。
如果还要按项目或者应用维度拆分,再增加对应字段即可。
建表SQL示例:
CREATE TABLE user_token_usage (
id BIGINT UNSIGNED AUTO_INCREMENT PRIMARY KEY,
user_id VARCHAR(64) NOT NULL COMMENT '用户ID',
request_id VARCHAR(64) NOT NULL COMMENT '请求唯一ID',
model_name VARCHAR(64) NOT NULL DEFAULT '' COMMENT '模型名称',
prompt_tokens INT UNSIGNED NOT NULL DEFAULT 0 COMMENT '输入token数量',
completion_tokens INT UNSIGNED NOT NULL DEFAULT 0 COMMENT '输出token数量',
total_tokens INT UNSIGNED NOT NULL DEFAULT 0 COMMENT '总量,可冗余',
created_at DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT '请求时间',
KEY idx_user_created (user_id, created_at),
KEY idx_request (request_id)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='用户token消耗明细';
total_tokens 可以不存,也可以作为冗余字段由应用层计算写入,方便后续直接按总量查询。
写入和统计查询怎么落地
写入时机安排在API响应返回以后。
伪代码逻辑如下:
usage = response.get('usage')
if usage:
insert(
user_id=user.id,
request_id=response.get('id'),
model_name=usage.get('model', ''),
prompt_tokens=usage.get('prompt_tokens', 0),
completion_tokens=usage.get('completion_tokens', 0),
total_tokens=usage.get('total_tokens', 0)
)
统计某段时间内每个用户的输入输出消耗:
SELECT
user_id,
SUM(prompt_tokens) AS total_input,
SUM(completion_tokens) AS total_output,
SUM(total_tokens) AS total_usage
FROM user_token_usage
WHERE created_at >= '2025-01-01 00:00:00'
AND created_at < '2025-02-01 00:00:00'
GROUP BY user_id
ORDER BY total_usage DESC;
如果想按天查看每个用户的使用趋势,可以改成这样:
SELECT
DATE(created_at) AS day,
user_id,
SUM(prompt_tokens) AS total_input,
SUM(completion_tokens) AS total_output
FROM user_token_usage
GROUP BY day, user_id
ORDER BY day DESC;
避坑提醒
第一,不要把输入输出Token塞进同一个字段再加类型标记,否则查询时要用 CASE WHEN,索引和汇总都很别扭。
第二,务必在请求日志中记录 request_id,并给 user_id + request_id 建立唯一索引,这样能防止同一请求被重复统计。
第三,Token数量必须依赖上游API返回的 usage 字段,不要自己按字符数估算,因为不同模型的tokenizer不一样,估算结果没有参考价值。
第四,如果流水表数据量增长很快,建议按月分表或者定期归档,否则全表聚合会越来越慢。
验证最终效果
正确落地后,查询结果应满足:每一行的 total_input + total_output 等于该请求的 total_usage,按用户分组后也能看到独立的输入输出数值。
建议先用一条真实API调用插入测试数据,再执行上面的 SELECT 语句验证,确认数值与API返回的usage一致后再接入线上统计。