MongoDB用BSON(Binary JSON)格式存储文档,BSON在JSON的基础上扩展出了更丰富的类型系统,除了字符串、数字、数组、对象之外,还支持日期、ObjectId、二进制、正则表达式等。不过在日常开发中,打交道最多的还是字符串、数字、数组、对象这四种。很多人写代码时习惯按JSON的思路理解MongoDB的数据类型,结果在数字精度、类型比较、数组查询上踩了不少坑。这篇文章把这四种类型逐一拆开讲清楚,包括存储规则、Shell中的写法以及实际使用中的注意事项。

字符串类型:String与UTF-8编码
MongoDB中的字符串统一采用UTF-8编码,这意味着中文、英文、emoji都可以直接存储,不需要任何额外配置。这也是它相比一些传统数据库在国际化场景下的天然优势。BSON中的字符串最大长度受单个文档16MB的限制约束,实际业务中几乎不会触顶,但如果是存储大文本,建议放在GridFS或对象存储里,而不是硬塞进文档字段。
需要特别注意的是字符串的类型编号。BSON为每个类型分配了编号,String是2。在查询中可以用$type操作符按类型过滤,例如{name: {$type: 2}}可以查出name字段为字符串的所有文档,也可以直接写{$type: "string"},两种写法等价。这在排查脏数据时非常实用,比如一个字段本应全是字符串,却混入了数字,用类型查询能快速定位问题记录。
// 插入包含中文字符串的文档
db.users.insertOne({
name: "张三",
nickname: "三哥🚀",
email: "zhangsan@ipipp.com"
})
// 按类型查询:找出 name 是字符串类型的文档
db.users.find({ name: { $type: "string" } })
// 字符串的精确匹配与正则匹配
db.users.find({ name: "张三" })
db.users.find({ name: /^张/ })</code>还有一个容易被忽略的点:字符串比较是按二进制逐字节进行的,也就是基于UTF-8编码的字节序。对于纯英文没什么问题,但中文的排序结果可能和拼音顺序、笔画顺序都不一致。如果业务对排序有要求,需要在写入时额外冗余一个排序键字段,或者由应用层排序。
数字类型:Int32、Int64与Double的区别
JSON只有一种数字类型,而BSON将其细分为三类:32位整数(int)、64位整数(long)和64位浮点数(double)。这三种类型在比较上是互相兼容的,1、NumberInt(1)和NumberLong(1)在相等判断上视为相等,但在存储空间和精度表现上有差异,混用不当会带来隐蔽的bug。
在mongoshell中,直接写{age: 25}默认会被解释为double类型。如果想明确存储为int或long,需要用包装函数:
// 默认写入的是 double
db.products.insertOne({ price: 100 })
// 明确指定为 32 位整数
db.products.insertOne({ stock: NumberInt(500) })
// 明确指定为 64 位整数,适合雪花ID、时间戳毫秒值
db.orders.insertOne({ orderId: NumberLong("7350123456789123456") })
// 查询时注意类型匹配
db.products.find({ stock: NumberInt(500) }) // 精确匹配 int 类型这里有个经典大坑:JavaScript的Number.MAX_SAFE_INTEGER是2的53次方减1,超过这个范围的long值如果经由驱动以普通JS数字传入,会丢失精度。所以传递64位大整数时,一定要用字符串构造NumberLong("..."),或者在应用层使用各语言驱动提供的Decimal、Long类型。同理,金额计算不要依赖double,浮点数的二进制表示无法精确表达0.1这类十进制小数,涉及货币应该使用NumberDecimal(Decimal128类型),它最多支持34位有效十进制数字,能保证金额计算的正确性。
// 金额字段使用 Decimal128
db.accounts.insertOne({
balance: NumberDecimal("1234.567890123456789012345678901234")
})
// 对比:double 的精度问题
0.1 + 0.2 // 结果是 0.30000000000000004数组类型:灵活但需谨慎的多值字段
数组是BSON中非常灵活的类型,元素可以是任意BSON类型的混合,比如["a", 1, true, {x: 1}]是完全合法的。MongoDB对数组的查询支持也很强大,查询数组字段时会自动遍历元素匹配,例如{tags: "hot"}能匹配tags数组中含有"hot"的文档,不需要像关系型数据库那样做中间表关联。
数组的一个重要机制是多键索引(Multikey Index)。对数组字段建索引时,MongoDB会自动为数组中的每个元素生成一个索引键,因此按数组元素查询同样能走索引。但要清楚它的限制:一个复合索引中最多只能有一个数组字段,如果两个字段都是数组,插入时会直接报错。此外,数组元素数量过多会导致索引膨胀,一个有一万个元素的数组就意味着一万个索引项,写入性能会明显下降。
// 插入带数组的文档
db.articles.insertOne({
title: "MongoDB入门",
tags: ["mongodb", "nosql", "database"],
comments: [
{ user: "李四", content: "写得不错" },
{ user: "王五", content: "学到了" }
]
})
// 数组元素匹配查询
db.articles.find({ tags: "nosql" })
// 匹配同时包含多个元素的数组
db.articles.find({ tags: { $all: ["mongodb", "nosql"] } })
// 匹配数组中满足多个条件的同一个元素
db.articles.find({
comments: { $elemMatch: { user: "李四", content: "写得不错" } }
})
// 对数组字段建立多键索引
db.articles.createIndex({ tags: 1 })上面的$elemMatch值得单独说一句:如果写{"comments.user": "李四", "comments.content": "写得不错"},MongoDB会允许不同元素分别满足两个条件,即李四的评论和王五的那条“写得不错”联合命中;而$elemMatch要求同一个元素同时满足所有条件。这是数组嵌套查询中最常见的语义错误来源,务必区分清楚。
对象类型:嵌套文档的访问与建模
对象(即内嵌文档)是MongoDB文档模型的核心。一个文档内可以嵌套多层对象,最大嵌套深度为100层,配合16MB的文档大小上限,理论上能表达非常复杂的数据结构。嵌套对象把原本需要JOIN关联的数据聚合在一起,读取时一次查询即可拿到完整数据,这是MongoDB高性能的重要来源。
访问嵌套字段使用点号路径,例如"address.city"。注意路径要用引号包裹,因为字段名中含有.时必须加引号,否则语法报错。更新嵌套字段同样用点号:
// 插入嵌套对象的文档
db.customers.insertOne({
name: "赵六",
address: {
city: "上海",
street: "南京路100号",
geo: { lat: 31.23, lng: 121.47 }
}
})
// 点号路径查询嵌套字段
db.customers.find({ "address.city": "上海" })
db.customers.find({ "address.geo.lat": { $gt: 31 } })
// 更新嵌套字段(只改 city,不影响 address 下其他字段)
db.customers.updateOne(
{ name: "赵六" },
{ $set: { "address.city": "北京" } }
)
// 整体替换嵌套对象(street 会丢失)
db.customers.updateOne(
{ name: "赵六" },
{ $set: { address: { city: "北京" } } }
)上面最后两个更新操作的差异要特别留意:用点号路径是精确修改某个子字段,而直接对address赋值是整体替换。误用整体替换导致子字段丢失,是新手更新嵌套文档时的高频事故。
建模层面,嵌套对象适合“一对一”或“一对少量”的关系,比如用户与收货地址、文章与评论的前几十条。如果嵌套的子文档会无限制增长(比如一条商品下的所有订单),就应该拆分为独立集合,否则文档反复迁移、16MB上限、并发写冲突都会成为隐患。判断标准很简单:子数据是否随时间无限增长、是否需要单独查询。满足任一条,就值得拆出去。
类型混用与常见误区总结
MongoDB是弱schema的,同一个字段在不同文档里可以是不同类型,这带来灵活性的同时也埋下了隐患。查询时的类型排序规则是:Null < Numbers < String < Object < Array < BinData < ObjectId < Boolean < Date。也就是说,对一个混杂类型的字段排序或做范围查询,结果可能不符合直觉。生产环境建议通过JSON Schema校验($jsonSchema)约束字段类型,把问题拦截在写入阶段。
// 集合级类型校验:stock 必须是 int,tags 必须是数组
db.createCollection("products", {
validator: {
$jsonSchema: {
required: ["name", "stock"],
properties: {
name: { bsonType: "string" },
stock: { bsonType: "int", minimum: 0 },
tags: { bsonType: "array" }
}
}
}
})归纳几个实践建议:数字类型在写入时就确定好int、long或decimal,避免同一个字段三种类型并存;数组元素保持类型一致,便于建索引和查询;嵌套深度控制在两三层以内,过深的路径既难维护也影响查询语句的可读性;对金额、ID等精度敏感字段坚持使用NumberDecimal和NumberLong。掌握这些细节,MongoDB的灵活才能真正为你所用,而不是变成线上事故的温床。
MongoDB数据类型MongoDB文档BSON修改时间:2026-09-05 23:33:09