MongoDB常见问题及性能优势

MongoDB的应用场景、优化技巧和常见问题,是选择MongoDB时关注的焦点。作为NoSQL数据库的一个分支,MongoDB在主流架构中得到广泛使用。

MongoDB性能问题处理

MongoDB日志检查

默认情况下,MongoDB会记录所有超过100毫秒的查询。MongoDB日志位置,通过systemLog.path=/var/log/mongodb/mongod.log定义。日志文件可能会很大,如果希望在分析之前清理它。在mongo命令行中输入:

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

查看MongoDB日志

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查询分析

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()

MongoDB创建索引

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()指定时间限制(以毫秒为单位),例如,允许100毫秒(十分之一秒)查询user集合中city字段中以字母“A”开头的文档:

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

应该为需要长时间的命令,设置一个合理的maxTimeMS值。不幸的是,MongoDB不允许定义全局的超时值,并且只能为单个查询设置它(尽管某些连接池可能会自动应用默认值)。

重建索引

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

db.user.reIndex();

如果,其他方法都失败了,可以考虑通过数据库修复来查找并解决问题。这应被视为最后的手段,在其他选项都已用尽时。在推进之前,建议使用mongodump或其他合适的方法进行完整备份。

MongoDB的优势与适用场景

高负载读写

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

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

基于位置的查询

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

非结构化数据

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

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

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

模式自由(schema-free),意味着对于存储在mongodb数据库中的文件,不需要知道它的任何结构定义。如果需要的话,完全可以把不同结构的文件存储在同一个数据库里。

存储在集合中的文档,被存储为键-值对的形式。键用于唯一标识一个文档,为字符串类型,而值则可以是各种复杂的文件类型。我们称这种存储形式为BSON(Binary JSON)。

支持大容量的存储

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

内置Sharding,提供基于Range的Auto Sharding机制:一个collection可按照记录的范围,分成若干个段,切分到不同的Shard上。

Shards可以和复制结合,配合Replica sets能够实现Sharding+fail-over,不同的Shard之间可以负载均衡。查询对客户端是透明的。客户端执行查询,统计,MapReduce等操作,这些会被MongoDB自动路由到后端的数据节点。

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

MongoDB的缺点

不支持事务

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

空间浪费

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

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

2、字段名所占用的空间:为了提高查询性能,mongodb把每个字段的key-value都以BSON的形式存储,如果存放数字类型的数据,则数据的overhead是最大的。一般需要把字段名缩减,避免占用空间过大,需要在易读性与空间占用上权衡。

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

4、需要定期运行db.repairDatabase()来整理记录,这个过程比较缓慢。