同一个字段里有的存了 int,有的存了 long、double,甚至 Decimal128,查询时算不算相等?大整数会不会丢精度?这篇用真实的 mongod 把相等、比较、唯一索引和聚合的类型提升逐项验证。
test> db.t.find({v: 1}).toArray()
[
{ _id: 'int32', v: 1 },
{ _id: 'int64', v: Long('1') },
{ _id: 'double', v: 1 },
{ _id: 'decimal', v: Decimal128('1') }
]
test> db.t.aggregate([{$project: {t: {$type: "$v"}}}])
[
{ _id: 'int32', t: 'int' },
{ _id: 'int64', t: 'long' },
{ _id: 'double', t: 'double' },
{ _id: 'decimal', t: 'decimal' },
{ _id: 'str', t: 'string' }
]
test> db.t.find({v: NumberLong("1")}).toArray().map(d => d._id)
[ 'int32', 'int64', 'double', 'decimal' ]查询 {v: 1} 会命中 int32、int64、double、Decimal128 存的 1,但不命中字符串 "1"。$type 可以看出实际存的类型。
test> db.big.find({v: 9007199254740992}).toArray().map(d => d._id)
[ 'long_2^53', 'double_2^53' ]
test> db.big.find({v: NumberLong("9007199254740993")}).toArray().map(d => d._id)
[ 'long_2^53+1' ]
test> db.big.find({v: 9007199254740993}).toArray().map(d => d._id) // JS 里这个字面量已经是 9007199254740992
[ 'long_2^53', 'double_2^53' ]
test> db.big.aggregate([{$project: {_id: 1, gt: {$gt: ["$v", 9007199254740992]}}}])
[
{ _id: 'long_2^53', gt: false },
{ _id: 'long_2^53+1', gt: true },
{ _id: 'double_2^53', gt: false }
]服务端比较 int64 与 double 时按精确数值,不会因为 double 精度不够而相等。问题往往出在客户端:mongosh 里写超过 2^53 的 JS 数字,字面量本身已经丢精度,要用 NumberLong("…")。
test> db.d.find({v: 0.1}).toArray().map(d => d._id)
[ 'double 0.1' ]
test> db.d.find({v: Decimal128("0.1")}).toArray().map(d => d._id)
[ 'decimal 0.1' ]
test> db.d.find({v: 0.5}).toArray().map(d => d._id)
[ 'decimal 0.5', 'double 0.5' ]
test> db.d.aggregate([{$limit: 1}, {$project: {_id: 0, double_sum: {$add: [0.1, 0.2]}, decimal_sum: {$add: [Decimal128("0.1"), Decimal128("0.2")]}}}])
[ { double_sum: 0.30000000000000004, decimal_sum: Decimal128('0.3') } ]0.1 的 double 不等于十进制的 0.1,所以两者互不命中;0.5 在二进制里精确,两种都命中。金额等需要十进制精确的场景用 Decimal128。
test> db.u.insertOne({v: 1.0})
E11000 duplicate key error collection: test.u index: v_1 dup key: { v: 1 }
test> db.u.insertOne({v: NumberLong("1")})
E11000 duplicate key error collection: test.u index: v_1 dup key: { v: 1 }
test> db.u.insertOne({v: Decimal128("1")})
E11000 duplicate key error collection: test.u index: v_1 dup key: { v: 1 }
test> db.u.insertOne({v: "1"})
ok唯一索引按数值判断重复,1、1.0、NumberLong(1)、Decimal128(1) 是同一个值;字符串 "1" 是另一个类型。
test> db.s.aggregate([{$project: {_id: 0, int_plus_int: {$add: ["$a", "$b"]}, t: {$type: {$add: ["$a", "$b"]}}, int_plus_double: {$type: {$add: ["$b", 0.5]}}, int_plus_long: {$type: {$add: ["$b", NumberLong("1")]}}, int_plus_decimal: {$type: {$add: ["$b", Decimal128("1")]}}}}])
[
{
int_plus_int: Long('2147483648'),
t: 'long',
int_plus_double: 'double',
int_plus_long: 'long',
int_plus_decimal: 'decimal'
}
]
test> db.m.aggregate([{$group: {_id: null, s: {$sum: "$v"}}}, {$project: {s: 1, t: {$type: "$s"}}}])
[ { _id: null, s: Long('2147483648'), t: 'long' } ]
test> db.m2.aggregate([{$group: {_id: null, s: {$sum: "$v"}}}, {$project: {s: 1, t: {$type: "$s"}}}])
[ { _id: null, s: 3, t: 'int' } ]int32 加法溢出自动变 int64;遇到 double 结果是 double,遇到 Decimal128 结果是 decimal;没有溢出时保持 int32。
test> db.t.find({v: {$type: "number"}}).toArray().map(d => d._id)
[ 'int32', 'int64', 'double', 'decimal' ]
test> db.t.find({v: {$type: ["int", "long"]}}).toArray().map(d => d._id)
[ 'int32', 'int64' ]
test> db.x.insertOne({_id: 1, big: 9007199254740993, big2: NumberLong("9007199254740993")}); db.x.aggregate([{$project: {_id: 0, big: 1, t1: {$type: "$big"}, big2: 1, t2: {$type: "$big2"}}}])
[
{
big: 9007199254740992,
big2: Long('9007199254740993'),
t1: 'double',
t2: 'long'
}
]"number" 是 int、long、double、decimal 的别名;也可以传数组列出几种类型。