MongoDB FAQ

MongoDB在主流架构中已得到广泛应用,应用场景及优化技巧成为关注焦点。

性能问题

日志

默认MongoDB只记录超过100毫秒的查询。

MongoDB日志位置通过systemLog.path=/var/log/mongodb/mongod.log定义。

日志文件可能很大,清理命令:

use admin;
db.runCommand({ logRotate : 1 });

查看日志

tail -f /var/log/mongodb/mongod.log

默认值通常是合理的,可通过配置日志级别或修改profiling parameters,将查询时间改为100毫秒以外的值。

最初可将设置为一秒钟以捕获严重的超时查询,在每组成功修复后将其减半,借助日志定位潜在瓶颈,如:

2016-02-12T11:05:08.161+0000 I COMMAND  
    [conn563] command project.$cmd 
    command: count { 
        count: "test", 
        query: { published: { $ne: false }, 
        country: "uk" } 
    } 
    planSummary: IXSCAN { country: 1 } 
    keyUpdates:0 
    writeConflicts:0 
    numYields:31 
    reslen:44 
    locks: { 
        Global: { 
            acquireCount: { r: 64 } 
        }, 
        MMAPV1Journal: { 
            acquireCount: { r: 32 } 
        }, 
        Database: { 
            acquireCount: { r: 32 } 
        }, 
        Collection: { 
            acquireCount: { R: 32 } 
        } 
    } 403ms

查询分析

MongoDB提供explain工具,意在揭示数据库操作工作过程,可在查询中添加explain,如:explain('executionStats')。

db.user.find(
  { country: 'AU', city: 'Melbourne' }
).explain('executionStats');

或将explain附加到集合上:

db.user.explain('executionStats').find(
  { country: 'AU', city: 'Melbourne' }
);

将返回一个大的JSON结果,重点检查两个值:

executionStats.nReturned - 返回文档的数量
executionStats.totalDocsExamined - 查找时所扫描的文档数。

若扫描的文档数量大大超过返回数,证明查询效率可能不高。最糟糕的情况MongoDB必须扫描集合中每个文档。因此,可利用索引执行查询。

更多信息和示例参阅MongoDB手册Analyze Query Performancedb.collection.explain()

索引

NoSQL数据库的索引由一或多个字段构成。

如,可索引user集合中的country字段,当搜索“AU”时通过索引执行查询,MongoDB可在索引中找到它,并引用匹配文档,无需扫描整个user集合。

createIndex可创建索引,示例中创建的索引以升序方式处理user集合country字段:

db.user.createIndex({ country: 1 });

大多数索引都是单字段,也可在两个或多个字段上创建复合索引。如:

db.user.createIndex({ country: 1, city: 1 });

更多索引选项参阅MongoDB手册索引简介

排序

以“地区代码”升序方式返回所有用户:

db.user.find().sort({ country: 1 });

若未定义索引,MongoDB须对结果进行排序,在分析大量返回文档时会出现问题。

数据库对排序操作施加了32MB的内存限制,根据经验,1,000个相对较小的文档足以将其推到边缘。

MongoDB不一定返回错误 - 只是一组空记录。

排序限制可能以意想不到的方式发生,假设,在country字段上有一个索引,如下所示:

db.user.createIndex({ country: 1 });

查询以country和city作为排序字段的升序操作:

db.user.find().sort({ country: 1, city: 1 });

虽然,country可以使用索引,但MongoDB仍需按辅助字段city排序,这会很慢,可能会超过32MB的内存限制。因此,应该创建一个复合索引:

db.user.createIndex({ country: 1, city: 1 });

排序操作现在已完全用到索引并将快速运行,还可按照反向顺序对country和city执行排序,因为,MongoDB可从索引的末尾开始排序。如:

db.user.find().sort({ country: -1, city: -1 });

但是,如果尝试以country降序city升序执行排序,则会出现问题:

db.user.find().sort({ country: -1, city: 1 });

将无法使用索引,因此,需禁止使用非索引的二级排序条件,或者创建另一个合适的索引:

db.user.createIndex({ country: -1, city: 1 });

同理,也可用于颠倒顺序的查询:

db.user.find().sort({ country: 1, city: -1 });

连接

构建应用程序时可使用单个数据库连接对象提高效率,该对象可重用于所有查询和更新。

MongoDB按照客户端连接接收顺序运行命令。

当应用程序可能对数据库执行异步调用时,命令会同步排队,且必须在下一个命令处理前完成。

如果有一个复杂的查询需要执行十秒钟,那么,其他人就不能在这个连接上同时与应用程序执行交互。

通过定义多个数据库连接对象可提高性能。如:

一个处理快速查询,一个处理较慢的文档插入和更新,一个处理复杂的报告生成。

每个对象都被视为一个单独的数据库客户端,不会延迟其他对象的处理,以保持应用程序快速响应。

执行时间

运行MongoDB命令时慢查询可以阻塞其他人,且最终可能导致web应用程序超时。

这会在Node.js程序中引发各种奇怪的不稳定问题,这些问题会继续等待异步回调。

可使用maxTimeMS()指定时间限制(以毫秒为单位),如:允许查询user集合中city字段以字母“A”开头的文档时最大执行时间为100毫秒:

db.user.find({ city: /^A.+/i }).maxTimeMS(100);

应为长时间执行的命令设置一个合理的maxTimeMS值。

不幸的是,MongoDB不允许定义全局超时时间,且只能为单个查询设置(尽管某些连接池会自动应用默认值)。

重建索引

若结构有效但查询运行缓慢,可尝试在每个集合上重建索引。如,用mongo命令行重建user集合索引:

db.user.reIndex();

若其他方法都失败了,可考虑通过数据库修复查找并解决问题。这应被视为最后的手段,在其他选项都已用尽时。

在推进前建议使用mongodump或其他合适的方法进行完整备份。

优势及应用场景

高负载读写

MongoDB关注高效插入,若需加载大量低价值业务数据,如日志收集,那么MongoDB将会是很好的选择,避免将其用在对事务安全要求高的场景,如交易处理。

MongoDB启动后将库中的数据以文件映射的方式加载到内存,若内存富裕将极大地改善数据库查询速度。

位置查询

MongoDB支持二维空间索引,如:管道,可快速精确的从指定位置获取数据。

非结构化数据

面向集合(Collenction-Oriented)存储,易存储对象类型的数据。

所谓“面向集合”(Collenction-Oriented)意思是数据被分组存储在数据集中,称为一个集合(Collenction)。每个集合在数据库中都有唯一的标识名,且可以包含无限数目的文档。

集合概念类似关系型数据库(RDBMS)中的表(table),不同之处是它不需要定义任何模式(schema)。

模式自由(schema-free)意味着对存储在mongodb数据库中的文件,无需知道它的结构定义。

若需要可把不同结构的文件存储在同一个数据库中。

集合中的文档以键-值对形式存储。

字符串类型的键用于唯一标识一个文档,值则是各种复杂的文件类型,这种存储形式被称为BSON(Binary JSON)。

大容量存储

MongoDB内置GridFS,GridFS是一个出色的分布式文件系统,可支持海量数据存储,满足对大数据集快速范围查询。

内置Sharding提供基于Range的Auto Sharding机制:

一个collection可按记录范围分成若干个段,切分到不同的Shard上。

Shards可与复制结合,配合Replica sets实现Sharding+fail-over,不同Shard间支持负载均衡。

查询对客户端是透明的。

客户端执行查询、统计、MapReduce等操作,会被MongoDB自动路由到后端的数据节点。

MongoDB的Sharding设计能力最大支持约20 petabytes,足以支撑一般应用。

缺点

不支持事务

对事务有要求的系统不适用。

空间浪费

官方FAQ中列举了以下几方面:

1、空间预分配:为避免磁盘碎片过多mongodb在空间不足时会申请生成大块的硬盘空间,申请量从64M、128M、256M指数递增,2G为单文件上限。

2、字段名占用空间:为提高查询性能mongodb将每个字段key-value以BSON形式存储,若存放数字类型数据,数据的overhead是最大的。

一般将字段名缩减避免空间占用过大,需在易读性与空间占用上作权衡。

3、删除记录不释放空间:为避免删除操作引发的大规模数据移动,记录在删除后所在的物理空间不会删除,只标记为“已删除”,可重复利用。

4、需要定期整理记录,这个过程比较缓慢。

db.repairDatabase()