日期、时区与 Decimal128 精度
本教程共 50 篇 · 第 10 篇 · 更新于 2026-07-30 · 约 7 分钟阅读
10. 日期、时区与 Decimal128 精度
本节目标:搞懂 MongoDB 怎么存时间(UTC)、应用层怎么处理时区,以及金额为什么必须用 Decimal128 而不是普通小数。
10.1 日期用 ISODate
MongoDB 的日期类型是 ISODate,写起来像这样:
// 写入一个时间
db.orders.insertOne({
_id: 1001,
created_at: ISODate("2024-01-15T08:30:00Z")
})
Note据 MongoDB 8.3 官方文档,BSON Date 类型是「UTC datetime」:底层是一个 64 位整数,表示从 Unix 纪元(1970-01-01)起的毫秒数。
10.2 时间存的是 UTC
关键点:MongoDB 内部统一按 UTC(世界协调时)存日期。你写进去的本地时间,会被换算成 UTC 存。
// 上海时间 16:30,等价于 UTC 08:30
db.orders.insertOne({ created_at: ISODate("2024-01-15T16:30:00+08:00") })
// 读出来看到的是 UTC
db.orders.findOne().created_at
ISODate("2024-01-15T08:30:00Z")
所以时区的处理责任在「应用层」:存的时候转 UTC,显示的时候再按用户所在时区格式化。
Tip最佳实践:所有时间都存 UTC,只在展示给用户时做时区转换。这样跨国业务不会对账出错。
10.3 时区常见坑
我踩过的坑:两个用户一个在北京、一个在纽约,同一时刻下单,如果用各自本地时间存,查「今天订单」就会乱。
- 存:统一转成 UTC 再写。
- 查范围:查询条件也用 UTC 时间。
- 显示:读出来后按用户时区格式化,库里不动。
// 查 2024-01-15 全天(UTC 视角)
db.orders.find({
created_at: {
$gte: ISODate("2024-01-15T00:00:00Z"),
$lt: ISODate("2024-01-16T00:00:00Z")
}
})
10.4 为什么金额不能用 Double
普通小数 19.99 在 MongoDB 里是 Double(双精度浮点)。浮点无法精确表示很多十进制小数。
// 浮点的老问题:0.1 + 0.2 不等于 0.3
0.1 + 0.2
0.30000000000000004
金额累加几千笔后,差额会肉眼可见。对账差一分钱,财务会找你。
10.5 Decimal128 解决精度
Decimal128 是 128 位十进制浮点,专为精确小数设计。
Note据 MongoDB 8.3 官方文档,Decimal128 支持 34 位十进制有效数字,指数范围 -6143 到 +6144,适合货币、税务、科学计算等对舍入敏感的场景。
写法是用 NumberDecimal(),且建议把值当字符串传,避免先被浮点污染:
// 用 NumberDecimal 存金额,传字符串保精度
db.orders.insertOne({
_id: 1001,
total: NumberDecimal("377.00"),
status: "paid"
})
查询和运算都按数值来,精度不丢:
// 找金额大于 100 的订单
db.orders.find({ total: { $gt: NumberDecimal("100.00") } })
Warning同一个字段别混用 Double 和 Decimal128。混了之后相等判断和排序可能出意外。要么全用 Decimal128,要么全用 Double,保持一致。
10.6 Double 还是 Decimal128
一句话总结:
- 计数、评分、温度这类「近似也无妨」的数值,用 Double 没问题。
- 钱、税率、利率这类「差一分都不行」的数值,用 Decimal128。
// 用 products 集合演示:价格用高精度小数更稳妥
db.products.insertOne({
_id: 1,
name: "机械键盘",
price: NumberDecimal("399.99"),
category: "外设",
stock: 50,
tags: ["hot"]
})