
作者:赵师的工作日(赵明中)
现役Oracle ACE、MySQL 8.0 ocp、TiDB PCTA\PCTP、Elasticsearch Certified Engineer
微信公众号:赵师的工作日
CSND:赵师的工作日
本文将介绍 MongoDB 开发中的一些最佳实践和常见注意事项。
一、数据建模规范
MongoDB中数据是以 BSON 格式(类似于 JSON)存储的。在设计数据库时,合理的 数据建模 是保证系统性能和可扩展性的关键。
1、选择正确的存储模型
MongoDB 提供了两种常见的存储模型:嵌套文档 和 引用文档。
- 嵌套文档(Embedding):将相关数据嵌套在同一个文档中,适用于查询时需要一次性获取所有数据的场景。适合一对多关系和高读取需求的情况。
示例:
{
"_id": 1,
"name": "John",
"address": {
"street": "123 Main St",
"city": "New York",
"zip": "10001"
}
}
- 引用文档(Referencing):在一个文档中存储另一个文档的引用,适用于数据频繁变化或有复杂关系的数据模型,避免了数据冗余。适合一对多、多对多关系的场景。
示例:
{
"_id": 1,
"name": "John",
"address_id": 101
}
{
"_id": 101,
"street": "123 Main St",
"city": "New York",
"zip": "10001"
}
选择建议:
- 如果查询需要频繁一次性加载所有数据,尽量使用嵌套文档;
- 如果数据量大且不常更新,使用引用文档;
- 如果涉及到复杂的多对多关系,尽量避免嵌套过深,选择引用文档。
2、限制文档大小
MongoDB 的单个文档最大大小为 16MB。设计文档时,应确保文档的大小不超过这一限制。避免文档过大,因为较大的文档可能会影响读取性能,尤其是在查询和更新时。
3、数据冗余与规范化
在 MongoDB 中,数据冗余并非坏事,但要适度。为了查询性能,有时可以通过冗余来避免复杂的查询操作。然而,过多的数据冗余会导致更新和删除操作变得困难。根据应用场景选择合适的数据模型:
- 对于读多写少的场景,可以适当冗余数据,减少查询时的连接开销;
- 对于写多读少的场景,应尽量避免冗余,以减少更新时的复杂性。
二、查询优化规范
在 MongoDB 中,查询性能直接影响系统的响应时间和整体效率。因此,合理设计查询语句和索引是非常重要的。
1、使用索引优化查询
MongoDB 提供了多种索引类型,可以加速查询性能:
- 单字段索引:为常用的查询条件字段创建索引。
示例:
db.users.createIndex({ "email": 1 })
- 复合索引:当查询中包含多个字段时,使用复合索引可以优化查询效率。需要注意的是,复合索引的顺序很重要,通常应将查询条件中选择性较高的字段放在前面。
示例:
db.orders.createIndex({ "user_id": 1, "status": 1 })
- 地理空间索引:如果有地理位置数据,使用地理空间索引可以优化地理位置查询。
示例:
db.locations.createIndex({ "location": "2dsphere" })
- 哈希索引和唯一索引:用于提高哈希查询和保证字段唯一性的效率。
索引注意事项:
- 索引的创建会消耗磁盘空间,并且每次插入、更新或删除操作时都会增加额外的成本,因此应该根据实际查询需求合理创建索引。
- 定期分析查询性能,删除不再使用的索引,避免冗余的索引增加存储负担和性能负担。
2、查询优化
- 避免全表扫描:使用索引时,避免不加限制的查询(如 find()),否则会导致全表扫描,性能差。使用适当的查询条件,尽量确保查询条件能够使用到索引。
- 使用投影(Projection):通过投影返回查询结果时,只获取需要的字段,避免返回不必要的数据,提高查询效率。
示例:
db.users.find({ "age": { $gte: 18 } }, { "name": 1, "age": 1 })
- 避免深度嵌套的查询:尽量避免深度嵌套的查询条件,避免查询效率低下。如果需要查询多个嵌套字段,考虑重构数据模型或使用聚合框架。
三、数据一致性与事务
虽然 MongoDB 是一个分布式数据库,并且在某些场景下不强制事务支持,但它也提供了 ACID 事务 来确保操作的一致性。事务可以保证多个操作的原子性,特别是对于涉及多个集合或多文档更新的操作。
1、使用事务管理复杂操作
MongoDB 从 4.x 版本开始支持多文档事务。对于一些涉及多个文档或跨多个集合的操作,可以使用事务来确保操作的原子性。
示例:
const session = client.startSession();
session.startTransaction();
try {
db.orders.insertOne({ ... }, { session });
db.inventory.updateOne({ _id: productId }, { $inc: { stock: -1 } }, { session });
session.commitTransaction();
} catch (error) {
session.abortTransaction();
} finally {
session.endSession();
}
事务使用建议:
- 尽量避免在性能要求较高的场景中使用事务,因为事务会增加额外的开销。
- 在高并发场景下,合理控制事务的范围,避免长时间占用锁。
2、数据一致性模型
- MongoDB 默认提供 最终一致性,即数据可能在一段时间内处于不一致状态,尤其是在分布式集群中。如果你的应用对数据一致性要求较高,可以通过调整写入策略、启用事务等方式来保证一致性。
- 在副本集模式下,设置合适的 写入关注级别(Write Concern)和 读取关注级别(Read Concern),以保证数据一致性。
四、错误处理与日志记录
1、错误处理
在 MongoDB 开发中,错误处理是确保系统稳定性的关键。常见的错误包括连接错误、查询错误、索引错误等。务必使用 try-catch 块捕获异常,记录详细的错误信息,并采取合适的恢复措施。
示例:
try {
const result = db.collection("users").find({ _id: 123 }).toArray();
} catch (error) {
console.error("Error occurred while querying the database:", error);
// 进一步处理错误
}
2、日志记录
合理的日志记录有助于监控 MongoDB 操作和排查问题。使用日志记录数据库操作和性能监控信息,有助于提前发现潜在的性能瓶颈和故障。
建议:
- 记录所有关键操作,如插入、更新和删除等。
- 使用 MongoDB 的 慢查询日志 监控慢查询,优化性能。
五、安全性与访问控制
1、用户认证与权限管理
MongoDB 提供了基于角色的访问控制(RBAC),允许对不同用户设置不同的权限。为不同的用户创建适当的角色,确保数据库的安全性。
示例:
db.createUser({
user: "app_user",
pwd: "password",
roles: [
{ role: "readWrite", db: "mydb" }
]
});
2、数据加密
为了确保数据的安全性,MongoDB 提供了多种加密方式,避免数据泄露和未经授权的访问。
- 传输加密:通过启用 TLS/SSL 加密传输,确保客户端与 MongoDB 服务器之间的数据传输过程不被中途拦截或篡改。
- 配置传输加密时,需配置服务器端和客户端的证书,以确保安全通信。
示例配置(mongod.conf):
net:
ssl:
mode: requireSSL
PEMKeyFile: /path/to/mongodb.pem
CAFile: /path/to/ca.pem
3、存储加密(Encryption at Rest)
MongoDB 还支持在磁盘存储的数据加密,确保即使数据被窃取,未经授权的用户也无法读取数据内容。
启用存储加密时,可以使用 MongoDB 的 内建加密引擎,或者通过第三方工具(如加密文件系统)实现。
示例(启用加密):
security:
enableEncryption: true
encryptionKeyFile: /path/to/keyfile
4、字段级加密
MongoDB 4.2 版本开始支持 客户端字段级加密,允许对文档中的某些字段进行加密。这样,即使数据库被攻击者访问,数据的敏感部分也能得到保护。
示例(启用字段级加密):
const encrypted = await encryptData('sensitiveData', encryptionKey);
await db.collection('users').insertOne({ name: 'John', ssn: encrypted });
5、访问控制和角色管理
MongoDB 使用 角色访问控制(RBAC) 来限制数据库用户的权限。通过为用户分配角色,可以控制他们对数据库的访问级别。MongoDB 提供了几个预定义的角色,如 read, readWrite, dbAdmin, userAdmin 等,开发人员也可以根据需要自定义角色。
例如,为应用创建一个具有特定权限的用户:
db.createUser({
user: "app_user",
pwd: "securepassword",
roles: [
{ role: "readWrite", db: "mydb" },
{ role: "dbAdmin", db: "admin" }
]
});
建议:
- 遵循 最小权限原则,只为每个用户分配必需的权限,避免过度授权。
- 定期审查用户权限和角色分配,确保没有过期或无用的权限。
六、性能监控与优化
MongoDB 的性能监控是确保系统高效运行的重要组成部分。以下是一些常见的性能优化建议和监控工具。
1、使用 explain 分析查询性能
MongoDB 提供了 explain() 方法,帮助开发人员分析查询的执行计划和性能瓶颈。explain() 返回的结果能够帮助开发人员理解查询如何使用索引,从而优化查询。
示例:
db.orders.find({ "status": "pending" }).explain("executionStats");
通过分析执行计划,开发人员可以发现:
- 查询是否在索引上执行;
- 是否存在全表扫描;
- 查询的效率是否达到预期。
2、启用日志记录和慢查询监控
MongoDB 支持日志记录功能,可以监控数据库的各类操作和性能数据。启用慢查询日志,可以帮助你发现性能瓶颈并及时进行优化。
慢查询日志设置:
operationProfiling:
slowOpThresholdMs: 100
mode: all
这个配置会记录所有执行超过 100 毫秒的查询,并可以通过日志进行分析。通过定期审查这些日志,开发人员能够发现哪些查询是性能瓶颈,并进行优化。
3、监控数据库状态
MongoDB 提供了多种 监控工具,如:
- MongoDB Atlas:提供全面的数据库监控,实时查看数据库的性能指标,如 CPU 使用率、内存占用、磁盘 I/O、查询延迟等。
- mongostat 和 mongotop:这些命令行工具可以帮助开发人员实时监控 MongoDB 的状态和活动。例如,mongotop 可以显示每个集合的读写操作统计信息。
例如,使用 mongotop 来监控某个数据库的操作:
mongotop mydb
4、缓存与数据压缩
对于频繁查询的数据,可以使用缓存(如 Redis)来减轻数据库压力。将热点数据缓存到内存中,可以显著提高读取性能,并减少数据库的负担。
此外,MongoDB 还提供了 数据压缩 选项,可以在存储数据时进行压缩,以减少存储占用。默认情况下,MongoDB 使用 WiredTiger 存储引擎,它支持数据压缩。
storage:
wiredTiger:
collectionConfig:
blockCompressor: snappy





