规则 | 要求 | 正确示例 | 错误示例 |
红线 | 隔离业务数据与系统元数据 | db_config(独立业务库) | admin、local、config |
规则 | 要求 | 正确示例 | 错误示例 |
前缀 | 建议 db_ 开头 | db_order | Order |
字符集 | 小写字母 + 下划线 | db_user_center | db.user.center |
长度 | ≤ 64 字节 | db_payment | db-payment |
t_ 前缀 + 小写字母 + 下划线,采用"模块_实体"格式。规则 | 要求 | 正确示例 | 错误示例 |
前缀 | 建议 t_ 开头 | t_order_detail | OrderDetail |
格式 | 模块_实体 | t_user_address | t_user-address |
禁用 | 不以 system. 开头 | t_system_config | system.config |
分表 | 时间后缀 | t_log_202403 | t_log$202403 |
// 推荐:语义清晰、风格统一{"_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 四种写法表示创建时间。开发人员经常写错字段名导致查询结果为空,排查耗时从分钟变成小时。统一命名后,开发效率显著提升。场景 | 解决方案 | 示例 |
数组元素过多 | 拆分为多个文档 | 用户动态:每条动态一个文档 |
存储大文件 | 使用 GridFS | 图片、视频、大型日志 |
大文本内容 | 业务层压缩 | HTML 内容压缩后存储 |
超大文件 | 对象存储 + URL 引用 | 文件存 COS,MongoDB 存 URL |
// 查看单个文档大小Object.bsonsize(db.collection.findOne({ _id: xxx }))// 查看集合平均文档大小(单位:字节)db.collection.stats().avgObjSize
posts 数组中。活跃用户发布数千条动态后,文档超过16MB,新动态无法写入,用户投诉发帖失败。改为每条动态独立文档 + 用户 ID 关联后,问题彻底解决。// 推荐:嵌套层级适中(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": "层级过深,查询与维护成本极高"}}}}}}}
$set 路径如 "a.b.c.d.e.f.g.value"。开发人员频繁写错路径导致配置更新失败,且无法为深层字段建立有效索引。扁平化重构后,配置更新和查询都变得简单高效。考量因素 | 优先选择【嵌入式】 | 优先选择【引用式】 |
读取模式 | 数据总是一起读取 | 数据经常需要单独读取 |
数据量 | 子数据量小且规模有限 | 子数据量大或呈无限增长趋势 |
更新频率 | 子数据很少发生独立更新 | 子数据频繁发生独立更新 |
关系类型 | 一对一、一对少量 | 一对多、多对多 |
共享性 | 子数据仅从属于一个父文档 | 子数据被多个文档高频共享 |
// 订单 + 订单项:嵌入式(总是一起查询,订单项不会独立存在){"_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("...")}
// ✅ 混合模式:订单中冗余商品名称和价格(快照),同时保留 productId 引用{"orderId": "ORD001","items": [{"productId": ObjectId("..."), // 引用(用于关联最新商品信息)"name": "商品A", // 冗余(下单时快照,避免商品改名影响历史订单)"price": NumberDecimal("99.00") // 冗余(下单时价格快照)}]}
// 不推荐:无边界、无限增长的数组{"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")}
readings 数组中。运行一年后,活跃设备的数组包含数十万条读数,文档超过16MB无法写入新数据。改用"分桶模式"(每小时一个文档)后,单文档大小稳定在100KB以内。场景 | 推荐类型 | 不推荐类型 | 潜在问题 |
日期时间 | 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 // 数字含义不明}
0.1 + 0.2 得到 0.30000000000000004,累计计算后与银行对账差异达数百元。改用 Decimal128 后,计算精确到分,对账完全准确。// 推荐:使用默认 ObjectId{ "_id": ObjectId("65f3a2b8c1d2e3f4a5b6c7d8") }// 可以:自定义递增 ID(需确保递增特性){ "_id": NumberLong("20240315000001") }// 禁止:随机字符串(影响写入性能){ "_id": "550e8400-e29b-41d4-a716-446655440000" }
validationLevel: "moderate" 模式:只验证新写入和被更新的文档,不验证已有文档(适合存量数据迁移场景)。collMod 命令。// 创建带验证规则的集合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 验证后,脏数据被拒绝写入,存量数据清洗后功能恢复正常。影响 | 说明 |
实例启动时间长 | 引擎启动时需要逐一加载所有集合的元数据信息 |
内存占用高 | 每个集合的元数据都会常驻缓存,挤占业务内存 |
文件句柄消耗 | 每个集合对应多个底层数据文件,易触碰系统上限 |
运维复杂 | 状态监控、数据迁移、大版本升级等日常操作的耗时将呈指数级成倍增加 |
备份超时或失败 | 遍历海量元数据极易导致物理备份任务严重超时,甚至可能完全无法生成物理备份 |
检查项 | 验证手段 | 通过标准 |
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 个 |
文档反馈