应使用 decimal128 存储金额,因其能精确表示34位十进制数,避免 double 的精度丢失和 int 存分的跨币种缺陷;mongodb 原生支持,但需用字符串构造、注意驱动版本及聚合中类型混用降级问题。

用 Decimal128 存金额,别用 double 或 int
金融场景下,double 会丢失精度(比如 19.99 + 0.01 可能算出 19.999999999999996),int 存分又容易在跨币种、小数位不一致(如日元无小数、比特币支持8位)时硬编码出错。MongoDB 原生支持 Decimal128,它按 IEEE 754-2008 实现,能精确表示任意 34 位十进制数,且服务端运算也保持精度。
实操建议:
- 插入时必须用
Decimal128构造器,不能靠 driver 自动转换(例如 Node.js 的new Decimal128("123.45"),不是new Decimal128(123.45)—— 后者仍是浮点字面量) - 查询聚合时,
$sum、$avg等累加操作对Decimal128字段天然支持,结果仍是Decimal128;但若混入int字段,整个结果会降级为double - 注意驱动兼容性:Python PyMongo ≥ 3.4、Node.js MongoDB Driver ≥ 3.0、Java Driver ≥ 3.4 才完整支持
Decimal128序列化/反序列化
流水记录加签名字段 hash 和 prev_hash 防篡改
单靠数据库权限控制无法防止管理员或内鬼修改历史流水。需要在应用层写入时生成可验证的链式哈希:每条记录包含自身内容的 SHA-256(不含 _id 和 hash 字段),以及上一条流水的 hash 值。这样任意一条被改,后续所有 prev_hash 校验都会失败。
实操建议:
- 计算
hash时,必须固定字段顺序、统一 JSON 序列化格式(推荐使用严格模式,如 Python 的json.dumps(..., sort_keys=True, separators=(',', ':'))),否则同样数据生成不同哈希 -
prev_hash初始值设为空字符串或全零"0000000000000000000000000000000000000000000000000000000000000000",第一条流水靠此识别 - 不要把私钥存在数据库里;签名用的密钥应由 KMS 或 HSM 托管,应用只调用签名接口
- 校验逻辑不应只查单条,而应从最新一条倒推,直到
prev_hash为空 —— 中间任一环断裂即说明被篡改
索引设计要覆盖高频查询路径:时间范围 + 账户 + 状态
交易流水最常查的是「某账户近7天的成功入账」或「某笔订单关联的所有流水」,单一 _id 索引完全不够。但建太多复合索引又拖慢写入,尤其金融系统写入压力大。
实操建议:
- 必建复合索引:
{ account_id: 1, created_at: -1, status: 1 }—— 支持按账户查最新流水,加status过滤(如只查"success") - 如果支持按外部订单号追溯,再加一个:
{ order_id: 1, created_at: -1 };注意order_id必须全局唯一且非空,否则索引选择性差 - 避免在
hash字段建索引:它长度固定但无业务排序意义,且更新频繁(虽然实际不更新),纯属浪费空间和内存 - 定期用
db.collection.explain("executionStats").find(...)验证查询是否命中索引,特别是聚合管道里的$match阶段
写入必须用 insertOne + writeConcern: { w: "majority", j: true }
金融流水一旦写入就不可撤回,必须确保不丢、不重、不乱序。默认的单节点写入(w: 1)在副本集主节点宕机瞬间可能丢失已确认但未同步的数据;不启用 j: true 则可能因断电丢失 journal 中尚未刷盘的记录。
实操建议:
- 永远不用
insertMany批量写入流水 —— 某条失败会导致整批回滚,无法定位哪一笔出问题;逐条insertOne并捕获每个WriteError更利于对账和重试 -
w: "majority"表示写入必须被大多数副本集成员确认,能扛住单节点故障;j: true强制等日志落盘,防断电丢数据 - 不要依赖
getLastError或旧版驱动的回调风格;现代驱动中,这些选项直接作为options传给insertOne - 如果业务允许轻微延迟,可考虑开启
readConcern: "majority"查询,避免读到尚未被多数节点确认的“幽灵数据”
真正难的不是选对类型或加个哈希,而是让所有服务(支付网关、清结算、对账系统)写入时都严格遵循同一套哈希构造规则和 writeConcern 策略。少一个环节松动,防篡改就形同虚设。










