一、操作场景
·命名规范发散: 同一项目中 OrderDetail、order_detail、orderdetail 等命名风格共存,增加了后期代码维护和数据排查的沟通成本。
·数组无界限增长: 习惯性地将持续产生的子数据(如用户的所有历史登录日志)不断 push 到同一个文档的数组中。如果缺乏截断机制,可能会触碰 MongoDB 单个 BSON 文档16MB的物理上限,导致部分写入请求受阻。
·字段类型漂移: 同一个字段(如 age)混杂着数字与字符串类型,不仅增加了前端解析的复杂度,在特定情况下还可能影响底层索引的效率,导致查询结果不符合预期。
·集合划分过细: 如果单个集群表过多(超过5000),会加重底层 WiredTiger 存储引擎的元数据管理负担,可能会拉长实例的启动时间,并在极端情况下增加内存开销。
·过度依赖拆表关联: 习惯性地沿用关系型数据库思维,将“订单主表”和“订单明细表”强行拆分为两个集合。这把原本可以通过单次内嵌读取(Embedding)完成的操作,退化为了多次网络 I/O 的往返调用。
这篇档的目标:帮助你充分发挥 MongoDB 文档模型的优势,避免用关系型数据库的思维来设计 MongoDB。
二、规范一:严禁使用系统库名
·核心动作:
业务数据绝对禁止占用或混用 MongoDB 的系统保留库(包含但不限于 admin、local、config)。
| 规则 | 要求 | 正确示例 | 错误示例 |
| 红线 | 隔离业务数据与系统元数据 | db_config(独立业务库) | admin、local、config |
·业务应用:
某团队为了图方便,直接在 admin 库中创建了业务集合来存储系统配置数据。由于 admin 库承担着用户鉴权与集群管理的核心职能,在执行管理类操作时可能需要获取高优先级的系统锁,与业务读写操作产生竞争,导致整个数据库响应急剧变慢。后续将配置数据迁移至独立的业务库后,性能才得以恢复。
三、规范二:数据库命名
·核心动作:
数据库名建议使用 db_ 前缀 + 小写字母 + 下划线。
·命名规则与示例:
| 规则 | 要求 | 正确示例 | 错误示例 |
| 前缀 | 建议 db_ 开头 | db_order | Order |
| 字符集 | 小写字母 + 下划线 | db_user_center | db.user.center |
| 长度 | ≤ 64 字节 | db_payment | db-payment |
·业务应用:
某研发团队创建了名为 UserCenter 的数据库,但在生产环境(Linux)时,部分微服务的连接字符串被误写为了 usercenter。由于 MongoDB 在 Linux 环境下对数据库名严格区分大小写,导致生产环境意外生成了一个全新的空库,核心业务查询大面积报空指针错误。此外,由于没有统一的前缀,DBA 在同一集群中清理历史废弃的临时库时,难以快速辨别哪些是核心业务库。全面实施 db_ 前缀加纯小写字母(如 db_user_center)的规范后,跨平台部署的库名大小写问题被根除,运维的安全边界也严密可控。
四、规范三:集合命名
·核心动作:
建议集合名使用 t_ 前缀 + 小写字母 + 下划线,采用"模块_实体"格式。
·命名规则与示例:
| 规则 | 要求 | 正确示例 | 错误示例 |
| 前缀 | 建议 t_ 开头 | t_order_detail | OrderDetail |
| 格式 | 模块_实体 | t_user_address | t_user-address |
| 禁用 | 不以 system. 开头 | t_system_config | system.config |
| 分表 | 时间后缀 | t_log_202403 | t_log$202403 |
·业务应用:
某项目使用 system.orders 作为订单集合名。由于 system. 是 MongoDB 保留前缀,某些底层管理操作会误将其识别为系统内部集合而直接跳过,最终导致自动备份数据不完整,改名为 t_orders 后问题解决。
五、规范四:字段命名
·核心动作:
建议统一采用 camelCase (小驼峰)或 snake_case (蛇形)命名域。字段设计需做到“自解释”,避免过度描述。严禁使用无意义的缩写。
·字段命名对比:
// 推荐:语义清晰、风格统一{ "_id": ObjectId("…"), "userName": "张三", // camelCase 风格 "createTime": ISODate("…"), "orderItems": […], "totalAmount": 199.00}// 不推荐:命名混乱{ "_id": ObjectId("…"), "UN": "张三", // 缩写不清晰 "Create_Time": ISODate("…"), // 混合风格 "oi": […], // 含义不明 "_total": 199.00 // 业务字段以 _ 开头,易与系统字段冲突}
·业务应用:
某团队字段命名不统一,同一个集合中有 createTime、Create_Time、create_time、CT 四种写法表示创建时间。开发人员经常写错字段名导致查询结果为空,排查耗时从分钟变成小时。统一命名后,开发效率显著提升。
六、规范五:控制单文档大小
·核心动作:单文档大小建议控制在100KB以内,不超过16MB限制。文档体积越大,对读写性能、内存占用和网络传输的影响越显著。
·大文档处理策略:
| 场景 | 解决方案 | 示例 |
| 数组元素过多 | 拆分为多个文档 | 用户动态:每条动态一个文档 |
| 存储大文件 | 使用 GridFS | 图片、视频、大型日志 |
| 大文本内容 | 业务层压缩 | HTML 内容压缩后存储 |
| 超大文件 | 对象存储 + URL 引用 | 文件存 COS,MongoDB 存 URL |
·检测文档大小的方法:
// 查看单个文档大小Object.bsonsize(db.collection.findOne({ _id: xxx }))// 查看集合平均文档大小(单位:字节)db.collection.stats().avgObjSize
·业务应用:
某社交平台将用户的所有动态存储在用户文档的 posts 数组中。活跃用户发布数千条动态后,文档超过16MB,新动态无法写入,用户投诉发帖失败。改为每条动态独立文档 + 用户 ID 关联后,问题彻底解决。
七、规范六:控制嵌套层级
·核心动作:
文档的嵌套层级建议控制在 3-5 层以内,避免过深的嵌套逻辑。
// 推荐:嵌套层级适中(3 层){ "_id": ObjectId("…"), "orderId": "ORD202403001", "customer": { // 第 1 层 "name": "张三", "contact": { // 第 2 层 "phone": "13800138000", "email": "zhangsan@example.com", "address": { // 第 3 层 "city": "西安", "street": "科技路" } } }, "items": [{ "productId": "P001", "quantity": 2 }] // 第 1 层}// 不推荐:嵌套过深(5 层以上){ "level1": { "level2": { "level3": { "level4": { "level5": { "level6": { "data": "层级过深,查询与维护成本极高" } } } } } }}
·业务应用:
某配置系统设计了8层嵌套的配置结构,修改最内层配置需要构造复杂的 $set 路径如 "a.b.c.d.e.f.g.value"。开发人员频繁写错路径导致配置更新失败,且无法为深层字段建立有效索引。扁平化重构后,配置更新和查询都变得简单高效。
八、规范七:嵌入式 vs 引用式设计
·核心动作:
优先考虑嵌入式设计,仅在数据量庞大或频繁独立更新等必要场景下使用引用式设计。
·设计决策表:
| 考量因素 | 优先选择【嵌入式】 | 优先选择【引用式】 |
| 读取模式 | 数据总是一起读取 | 数据经常需要单独读取 |
| 数据量 | 子数据量小且规模有限 | 子数据量大或呈无限增长趋势 |
| 更新频率 | 子数据很少发生独立更新 | 子数据频繁发生独立更新 |
| 关系类型 | 一对一、一对少量 | 一对多、多对多 |
| 共享性 | 子数据仅从属于一个父文档 | 子数据被多个文档高频共享 |
·嵌入式设计示例:
// 订单 + 订单项:嵌入式(总是一起查询,订单项不会独立存在){ "_id": ObjectId("…"), "orderId": "ORD202403001", "customerId": "C001", "items": [ { "productId": "P001", "name": "商品A", "quantity": 2, "price": 99.00 }, { "productId": "P002", "name": "商品B", "quantity": 1, "price": 199.00 } ], "totalAmount": 397.00, "status": "paid", "createTime": ISODate("2024-03-15T10:30:00Z")}
·引用式设计示例:
// 用户 + 文章:引用式(文章经常独立查询,且数量无限增长)// 用户文档{ "_id": ObjectId("user_001"), "userName": "张三", "email": "zhangsan@example.com"}// 文章文档(通过 authorId 引用用户){ "_id": ObjectId("article_001"), "title": "MongoDB 最佳实践", "authorId": ObjectId("user_001"), // 引用用户 "content": "…", "createTime": ISODate("…")}
·混合模式(Hybrid)——将高频访问的子数据冗余嵌入,同时保留引用关系。
// ✅ 混合模式:订单中冗余商品名称和价格(快照),同时保留 productId 引用{ "orderId": "ORD001", "items": [ { "productId": ObjectId("…"), // 引用(用于关联最新商品信息) "name": "商品A", // 冗余(下单时快照,避免商品改名影响历史订单) "price": NumberDecimal("99.00") // 冗余(下单时价格快照) } ]}
·业务应用:
某电商系统将订单和订单项分为两个集合(关系型思维),查询订单详情需要先查订单、再查订单项、最后应用层拼接。大促期间接口延迟从50ms飙升至 800ms。改为嵌入式设计(订单项嵌入订单文档)后,单次查询即可返回完整数据,延迟降至30ms,代码也大幅简化。
九、规范八:数组设计注意事项
·核心动作:
单个数组元素数量建议不超过1000,严禁设计无限增长的数组。
·无限增长数组 vs 独立文档关联示例:
// 不推荐:无边界、无限增长的数组{ "userId": "user_10001", "orders": [ { "orderId": "ORD_001", "amount": 99.00, "date": ISODate("…") }, { "orderId": "ORD_002", "amount": 158.00, "date": ISODate("…") }, // … 活跃用户可能累积数万条订单,触发 16MB 限制 ]}// 推荐:将数组元素拆分为独立文档,通过外键关联// 用户文档{ "userId": "user_10001", "name": "张三", "orderCount": 1024}// 订单文档(独立集合){ "orderId": "ORD_001", "userId": "user_10001", // 外键关联 "amount": 99.00, "date": ISODate("2024-03-15T10:30:00Z")}
·业务应用:
某 IoT 平台将传感器的所有读数存储在设备文档的 readings 数组中。运行一年后,活跃设备的数组包含数十万条读数,文档超过16MB无法写入新数据。改用"分桶模式"(每小时一个文档)后,单文档大小稳定在100KB以内。
注意:说明:
十、规范九:选择正确的数据类型
·核心动作:
日期用 Date 类型,金额用 Decimal128,ID 用 ObjectId 或递增 Long。
·数据类型选择表:
| 场景 | 推荐类型 | 不推荐类型 | 潜在问题 |
| 日期时间 | Date | 字符串 | 无法使用原生的日期运算和范围查询优化 |
| 金融金额 | Decimal128 | Double | 浮点精度丢失,导致账务对账出现差异 |
| 文档主键 | ObjectId(默认) | 随机字符串 | 非递增的随机 ID 会导致频繁的页分裂,严重拖慢写入性能。ObjectId 的前4字节为秒级时间戳,具备大致递增特性,使得 B-Tree 索引的写入集中在尾部,避免了随机插入导致的页分裂 |
| 大整数 ID | NumberLong | 字符串 | 无法进行数值比较和范围排序 |
| 状态标志 | String(枚举值) | 数字 | 魔术数字(Magic Number)含义不明确,后期维护困难 |
·数据类型使用示例:
// 正确的类型使用{ "_id": ObjectId("65f3a2b8c1d2e3f4a5b6c7d8"), // ObjectId "orderId": NumberLong("20240315000001"), // 大整数 "amount": NumberDecimal("199.99"), // 金额用 Decimal128 "createTime": ISODate("2024-03-15T10:30:00Z"), // 日期用 Date "status": "paid" // 状态用字符串枚举}// 错误的类型使用{ "_id": "random-uuid-string", // 随机字符串影响性能 "orderId": "20240315000001", // 字符串无法数值排序 "amount": 199.99, // Double 有精度问题 "createTime": "2024-03-15 10:30:00", // 字符串无法日期运算 "status": 1 // 数字含义不明}
·业务应用:
某金融系统用 Double 类型存储金额,计算 0.1 + 0.2 得到 0.30000000000000004,累计计算后与银行对账差异达数百元。改用 Decimal128 后,计算精确到分,对账完全准确。
十一、规范十:_id 字段使用规范
·核心动作:
除非有特殊需求,首选默认的 ObjectId 作为 _id;若业务需要自定义 _id,建议具备递增特性。
·使用示例:
// 推荐:使用默认 ObjectId{ "_id": ObjectId("65f3a2b8c1d2e3f4a5b6c7d8") }// 可以:自定义递增 ID(需确保递增特性){ "_id": NumberLong("20240315000001") }// 禁止:随机字符串(影响写入性能){ "_id": "550e8400-e29b-41d4-a716-446655440000" }
·业务应用:
某系统使用随机 UUID 作为主键,随数据量增长,索引树的随机写入导致磁盘 I/O 剧增并触发频繁的页分裂,写入 QPS 从10,000锐减至3,000。改为单调递增的 ObjectId 后,利用顺序追加特性消除了 I/O 瓶颈,性能恢复正常。
十二、规范十一:为核心集合配置 Schema 验证
·核心动作:
通过 MongoDB 提供的 JSON Schema 验证功能,在数据库引擎层面确保数据写入的类型与格式一致性。
注意:说明:
·Schema 验证示例:
// 创建带验证规则的集合db.createCollection("t_users", { validator: { $jsonSchema: { bsonType: "object", required: ["userName", "email", "createTime"], properties: { userName: { bsonType: "string", minLength: 2, maxLength: 50, description: "用户名,必填,2-50 个字符" }, email: { bsonType: "string", pattern: "^[a-zA-Z0-9._%+-]+@[a-zA-Z0-9.-]+\\\\.[a-zA-Z]{2,}$", description: "邮箱,必填,需符合邮箱格式" }, age: { bsonType: "int", minimum: 0, maximum: 150, description: "年龄,可选,0-150 的整数" }, status: { enum: ["active", "inactive", "deleted"], description: "状态,枚举值" }, createTime: { bsonType: "date", description: "创建时间,必填" } } } }, validationLevel: "strict", // strict: 所有写入都验证 validationAction: "error" // error: 验证失败则拒绝写入});
·业务应用:
某电商系统的 price 字段没有类型约束,有的存数字 99.00,有的存字符串 "99.00",甚至有存对象 {value: 99}。价格排序和比较完全混乱,促销活动的"满100减20"逻辑失效。添加 Schema 验证后,脏数据被拒绝写入,存量数据清洗后功能恢复正常。
十三、规范十二:控制集合数量
·核心动作:
单个实例的总集合数量建议不超过5000个。
·集合数量过多的影响:
| 影响 | 说明 |
| 实例启动时间长 | 引擎启动时需要逐一加载所有集合的元数据信息 |
| 内存占用高 | 每个集合的元数据都会常驻缓存,挤占业务内存 |
| 文件句柄消耗 | 每个集合对应多个底层数据文件,易触碰系统上限 |
| 运维复杂 | 状态监控、数据迁移、大版本升级等日常操作的耗时将呈指数级成倍增加 |
| 备份超时或失败 | 遍历海量元数据极易导致物理备份任务严重超时,甚至可能完全无法生成物理备份 |
·业务应用:
某物联网平台初期采用“一设备一集合”模式,随着业务增长,单库产生超过50,000个集合。海量元数据导致内存开销激增,实例重启时间从秒级恶化至数十分钟。备份任务因需遍历庞大元数据而严重超时,甚至直接中断,无法完成全量备份。废弃按设备分表,将数据合并至单一的时间序列集合(Time Series Collection),通过 deviceId + timestamp 建立复合索引。优化后集合数降至个位数,启动与备份均恢复正常。
十四、数据建模检查清单
为确保规范落地,建议在项目上线评审阶段逐项对照以下清单进行检查:
| 检查项 | 验证手段 | 通过标准 |
| 1. 命名规范统一 | Review 所有涉及的库/集合/字段命名代码 | 完全符合这篇档的命名及前缀规则 |
| 2. 文档规模可控 | 抽样执行 Object.bsonsize(doc) | 通用文档大小建议控制在100KB以内,不超过16MB限制核心复杂文档大小建议控制在1MB以内 |
| 3. 嵌套层级合理 | 审查核心文档的 JSON 结构树 | 最大嵌套深度 ≤ 3-5 层 |
| 4. 规避无限数组 | 深入审查数据写入与追加逻辑 | 数组具备明确的业务上限,或已采用分桶模式/$slice 截断机制 |
| 5. 数据类型严谨 | Review 实体类的字段类型定义 | 日期建议用 Date,金额建议用 Decimal128 |
| 6. 开启 Schema 验证 | 检查建表脚本或 validator 配置 | 核心集合的必填项、字段类型、格式校验已全部配置约束 |
| 7. 单个实例总集合数量建议不超过5000 | 预估并执行 show collections 统计 | 单库内业务集合数量预估/实际值 ≤ 100 个 |