MongoDB 权威指南(四)

原文:The Definitive Guide to Mongodb

协议:CC BY-NC-SA 4.0

九、数据库管理

Abstract

在本章中,我们将带您了解一些可以在 MongoDB 服务器上执行的基本管理操作。我们还将展示如何自动化其中的一些活动,比如备份您的服务器。

在本章中,我们将带您了解一些可以在 MongoDB 服务器上执行的基本管理操作。我们还将展示如何自动化其中的一些活动,比如备份您的服务器。

因为 MongoDB 是一个非关系数据库系统,所以不需要数据库管理员执行的许多更传统的功能。例如,没有必要在服务器上创建新的数据库、集合或字段,因为 MongoDB 会在您访问它们时动态创建这些元素。因此,在绝大多数情况下,您不需要管理数据库和模式。

然而,这种不必预定义一切的自由可能会导致无意中创建元素,比如文档中无关的集合和字段。管理员和开发者偶尔需要从数据库中清除未使用的数据元素,尤其是在项目的开发阶段,此时变化通常很快。在最终确定解决方案和清理数据库之前,他们可能必须尝试许多方法。MongoDB 的易用性鼓励了这种探索性的开发模式;但是,这也可能导致数据存储中的混乱,因为创建数据结构所需的工作量几乎为零。

造成这种混乱的一个因素,也是 MongoDB 和 SQL 数据库之间的一个更重要的区别,是 MongoDB 中的所有对象和元素名称在所有平台上都是区分大小写的。因此,fooFoo集合名称指的是两个完全不同的集合。因此,您需要小心使用数据库和集合命名,以避免意外创建多个仅在名称大小写上不同的数据库。(然而,从 MongoDB 2.4 开始,有一个例外:您不能再创建名称只有大小写不同的数据库,因为这样做会产生错误。)

这些数据库的不同版本将会填满您的磁盘,并且由于允许开发者和系统的最终用户连接到不完整的或非预期的数据集,它们可能会给开发者和系统的最终用户带来许多困惑。

在本章中,您将学习如何执行以下所有任务:

  • 备份并恢复您的 MongoDB 系统。
  • 使用提供的MongoDB shell(通过mongo命令调用)执行常见任务。
  • 通过身份验证控制对服务器的访问。
  • 监控您的数据库实例。

然而,在深入研究这些任务之前,我们先来看看用来执行其中许多任务的工具。

使用管理工具

管理员需要适合于执行保持服务器平稳运行的日常任务的工具。MongoDB 包中有一些非常好的工具,以及一个不断发展的有用的第三方工具集合。以下部分涵盖了一些最重要的可用工具,以及如何使用它们。

蒙戈,蒙戈布控制台

作为管理员,您将使用的主要工具是mongo,MongoDB 控制台工具。mongo是一个基于 JavaScript 的命令行控制台实用程序。它类似于主流关系数据库提供的许多查询工具。然而,mongo有一个独特的锦囊妙计:它可以运行用 JavaScript 编写的程序,直接与 MongoDB 数据库交互。

这个控制台允许您用 JavaScript 编写与 MongoDB 的所有交互,然后将这些脚本存储在.js文件中,在需要时运行。事实上,mongo控制台中的许多内置命令本身就是用 JavaScript 编写的。

您可以将任何要在命令 shell 中输入的命令放入一个扩展名为.js的文件中,并在启动 shell 或在 shell 中使用load()函数时,通过简单地将文件名添加到命令行中来运行它们。shell 将执行文件的内容,然后退出。这对于运行重复命令列表很有用。

在本章中,我们将使用mongo控制台来演示您可以在 MongoDB 服务器上执行的许多管理任务,因为它是随 MongoDB 服务器一起分发的,所以我们可以保证它会在那里。

使用第三方管理工具

MongoDB 提供了几个第三方管理工具。MongoDB,Inc .在 MongoDB 网站上维护了一个页面,其中列出了当前可用的第三方工具。你可以在 http://docs.mongodb.org/ecosystem/tools/administration-interfaces/ 找到这个名单。

这些工具中有许多是基于 web 的,原则上类似于 phpMyAdmin for MySQL,但有些也是成熟的桌面 ui。

备份 MongoDB 服务器

新的 MongoDB 管理员应该学习的第一个技能是如何备份和恢复 MongoDB 服务器。用这些知识武装自己会让你在探索一些更高级的管理功能时感觉更舒服,因为你知道你的宝贵数据被安全地存储在某个地方。

创建备份 101

让我们从执行一个简单的备份开始,然后恢复它。在这个过程中,您将确保备份完好无损,并且您将看到一些说明备份和恢复功能如何工作的实际例子。一旦您对如何使用这些特性有了坚实的理解,您将能够继续探索 MongoDB 更高级的管理特性。

在这个简单的备份示例中,我们假设如下:

  • 您的 MongoDB 服务器运行在您当前登录的同一台机器上。
  • 您有足够的磁盘空间来存放转储文件,这些文件的大小最多与您的数据库相同。
  • 您的备份将在您的主目录中进行。这意味着您不必处理任何与权限相关的问题。

MongoDB 备份实用程序名为mongodump;该实用程序作为标准发行版的一部分提供。以下示例将正在运行的 MongoDB 服务器简单备份到指定的磁盘目录:

$> cd ∼

$> mkdir testmongobackup

$> cd testmongobackup

$> mongodump

mongodump运行时,您应该看到它输出如下所示的内容:

$ mongodump

connected to: 127.0.0.1

Tue May 21 20:52:58.639 all dbs

Tue May 21 20:52:58.640 DATABASE: blog to dump/blog

Tue May 21 20:52:58.640 blog.system.indexes to dump/blog/system.indexes.bson

Tue May 21 20:52:58.641 4 objects

Tue May 21 20:52:58.641 blog.system.profile to dump/blog/system.profile.bson

Tue May 21 20:52:58.645 3688 objects

Tue May 21 20:52:58.645 Metadata for blog.system.profile to dump/blog/system.profile.metadata.json

Tue May 21 20:52:58.645 blog.authors to dump/blog/authors.bson

Tue May 21 20:52:58.646 1 objects

Tue May 21 20:52:58.646 Metadata for blog.authors to dump/blog/authors.metadata.json

Tue May 21 20:52:58.646 blog.posts to dump/blog/posts.bson

Tue May 21 20:52:58.686 29997 objects

Tue May 21 20:52:58.709 Metadata for blog.posts to dump/blog/posts.metadata.json

Tue May 21 20:52:58.710 blog.tagcloud to dump/blog/tagcloud.bson

Tue May 21 20:52:58.710 1 objects

Tue May 21 20:52:58.710 Metadata for blog.tagcloud to dump/blog/tagcloud.metadata.json

如果您的输出看起来与此不太相似,那么您应该仔细检查您的环境是否与前面陈述的假设相匹配。

如果您确实看到了正确的输出,那么您的数据库已经备份到了testmongobackup/dump目录。以下代码片段将数据库还原到执行备份时的状态:

$> cd ∼/testmongobackup

$> mongorestore --drop

connected to: 127.0.0.1

Tue May 21 20:53:46.337 dump/blog/authors.bson

Tue May 21 20:53:46.337 going into namespace [blog.authors]

Tue May 21 20:53:46.337 dropping

1 objects found

Tue May 21 20:53:46.338 Creating index: { key: { _id: 1 }, ns: "blog.authors", name: "_id_" }

Tue May 21 20:53:46.339 dump/blog/posts.bson

Tue May 21 20:53:46.339 going into namespace [blog.posts]

Tue May 21 20:53:46.339 dropping

29997 objects found

Tue May 21 20:53:47.284 Creating index: { key: { _id: 1 }, ns: "blog.posts", name: "_id_" }

Tue May 21 20:53:47.375 Creating index: { key: { Tags: 1 }, ns: "blog.posts", name: "Tags_1" }

Tue May 21 20:53:47.804 dump/blog/system.profile.bson

Tue May 21 20:53:47.804 skipping

Tue May 21 20:53:47.804 dump/blog/tagcloud.bson

Tue May 21 20:53:47.804 going into namespace [blog.tagcloud]

Tue May 21 20:53:47.804 dropping

1 objects found

Tue May 21 20:53:47.821 Creating index: { key: { _id: 1 }, ns: "blog.tagcloud", name: "_id_" }

--drop选项告诉mongorestore实用程序在恢复之前丢弃数据库中的每个集合。因此,备份的数据会替换数据库中当前的数据。如果您选择不使用--drop选项,恢复的数据将被附加到每个集合的末尾,这将导致重复的项目。

让我们更仔细地检查一下这个例子中发生了什么。

默认情况下,mongodump实用程序使用默认端口连接到本地数据库,提取与每个数据库和集合相关的所有数据,并将它们存储在预定义的文件夹结构中。

mongodump创建的默认文件夹结构采用以下形式:

./dump/[databasename]/[collectionname].bson

示例中使用的数据库系统由一个名为blog的数据库组成。blog数据库包含三个集合:authorspoststagcloud

mongodump将从数据库服务器获取的数据保存在.bson文件中,这些文件是 MongoDB 内部用来存储文档的内部 BSON 格式的副本。在前面的示例中,您还可以看到正在还原的每个集合的索引。MongoDB 服务器维护索引,它记录每个集合的索引定义,这些定义存储在metadata.json文件中。正是这些元数据文件允许您在从备份恢复时重建索引。

转储数据库后,您可以将文件夹存档并存储在任何在线或离线媒体上,如 CD、USB 驱动器、磁带或 S3 格式。

Note

在将备份文件写入目录之前,mongodump实用程序不会清空output目录的内容。如果您在此目录中有现有内容,它们不会被删除,除非它们与mongodump被指示备份的文件(collectionname .bson)的名称相匹配。如果您希望将多个集合转储添加到同一个转储目录,这很好;但是,如果每次备份数据时都使用同一个转储目录,但不清除它,这可能会导致问题。例如,假设您有一个定期备份的数据库,并且在某个时候您决定从该数据库中删除一个集合。除非您清除正在执行备份的目录,或者手动删除与已删除的集合相关联的文件,否则下次还原数据时,已删除的集合将会重新出现。除非您想在备份中覆盖数据,否则您应该确保在使用mongodump之前清空目标目录。

备份单个数据库

当您在同一台服务器上运行多个应用时,您通常会发现自己想要单独备份每个数据库,而不是像前面的示例那样一次备份所有数据库。

使用mongodump,您可以通过在命令行中添加-d database_name选项来实现这一点。这导致mongodump创建./dump文件夹;但是,该文件夹将只包含单个数据库的备份文件。

备份单个集合

假设您有一个博客站点,其中的authors集合的内容变化不大。相反,博客站点快速变化的内容包含在poststagcloud集合中。您可能一天只备份一次整个数据库,但希望每小时备份一次这两个集合。幸运的是,使用 mongodump,您可以通过使用-c选项来指定您希望备份的集合,从而轻松地做到这一点。

mongodump实用程序不会清除其目标目录。这意味着,对于您想要备份的每个集合,您可以连续调用mongodump来将一个给定的集合添加到您的备份中,如下例所示:

$mkdir ∼/backuptemp

$cd ∼/backuptemp

$mongodump -d blog -c posts

$mongodump -d blog -c tagcloud

...

archive the dump folder ∼/backuptemp away as a tar file

...

$ cd ∼

$ rm -rf backuptemp

深入了解备份

至此,您已经知道如何执行备份和随后恢复数据的基本任务。现在,您已经准备好查看一些强大的选项,这些选项允许您定制 MongoDB 的备份和恢复功能,以满足您的特定需求。

mongodump实用程序包括图 9-1 中所示的选项,通过在 MongoDB 2.5.3 中运行help来捕获。

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

图 9-1。

The mongodump utility help display showing its options

此处列出的大多数选项都是不言自明的,但以下选项除外:

  • --dbpath arg:如果您有大量的数据需要备份,并且您不关心索引的备份,那么最好通过将 MongoDB 服务器使用的数据文件直接复制到备份介质来备份数据库。该选项允许您直接从服务器的数据文件进行备份,但它只能在服务器脱机或被写保护的情况下使用(有关详细信息,请参阅本章后面的“备份大型数据库”一节)。
  • --directoryperdb:将这个命令行选项与--dbpath选项结合使用,指定正在备份的 MongoDB 服务器被配置为将其每个数据库的数据文件放在一个单独的目录中。默认情况下,MongoDB 将其所有数据文件放在一个目录中。仅当您已将服务器配置为在此模式下运行时,才应使用此选项。
    • o [ --out ] arg:使用该选项可以指定数据库转储的存放目录。默认情况下,mongodump实用程序在当前目录下创建一个名为/dump的文件夹,并将转储文件写入其中。您可以使用-o/--out选项来选择放置输出转储的替代路径。
  • --authenticationDatabase arg:指定保存用户凭证的数据库。Mongodump将默认使用没有该选项的–db指定的数据库。
  • --authenticationMechanism arg:默认为 MongoDB 的挑战/响应(用户名/密码)机制。这个命令用于切换到 MongoDB Enterprise edition 的 Kerberos 身份验证。

还原单个数据库或集合

您已经看到了mongodump实用程序如何备份单个数据库或集合;mongorestore实用程序具有同样的灵活性。如果从中恢复的转储目录中有所需集合或数据库的备份文件,您可以使用mongorestore来恢复项目;您不需要恢复备份中存在的所有项目。如果您愿意,可以单独还原它们。

让我们从查看mongorestore中可用的选项开始,如图 9-2 所示。

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

图 9-2。

The mongorestore help display showing its options

你可能从对mongodump的讨论中认识到这些选项中的大部分;但是,以下两个选项值得特别一提:

  • --drop:该选项指示mongorestore在恢复之前删除现有集合。这有助于确保没有重复。如果不使用此选项,还原的数据将被追加(插入)到目标集合中。
  • --noobjcheck:该选项指示mongorestore在将对象插入到destination集合之前忽略验证对象的步骤。

还原单个数据库

您可以使用mongorestore实用程序的-d选项来恢复单个数据库。和以前一样,如果数据库已经存在于您的 MongoDB 服务器中,不要忘记使用--drop选项:

$cd ∼/testmongobackup

$mongorestore -d blog --drop

还原单个集合

使用类似的语法将单个集合还原到数据库;不同之处在于,您还可以使用-c选项指定集合名称,如下例所示:

$cd ∼/testmongobackup

$mongorestore -d blog -c posts --drop

自动化备份

对于小型安装或开发者设置,运行mongodump实用程序并保存结果的简单操作是执行临时备份的一种非常合适的方法。例如,Mac OS X 工作站上的常见做法是让 Time Machine(Mac 备份实用程序)存储备份。

对于任何类型的生产设置,您都希望自动备份服务器;如果您遇到任何问题,定期备份可以帮助您避免麻烦或从麻烦中恢复。这不仅适用于您的安装(例如,如果您有损坏的数据库),而且适用于您的用户无意中损坏或破坏数据的情况。

让我们来看一些简单的脚本,您可以使用它们来自动化您的备份。

使用本地数据存储

如果您的系统连接了大型备份驱动器,或者您可以通过 NFS 或 SMB 挂载外部文件系统,那么一个在指定目录中创建归档文件的简单备份脚本就足够了。以下备份脚本易于设置;只需编辑脚本顶部的变量,以匹配本地系统的变量:

#!/bin/bash

##########################################

# Edit these to define source and destinations

MONGO_DBS=""

BACKUP_TMP=∼/tmp

BACKUP_DEST=∼/backups

MONGODUMP_BIN=/usr/bin/mongodump

TAR_BIN=/usr/bin/tar

##########################################

BACKUPFILE_DATE=date +%Y%m%d-%H%M``

# _do_store_archive <Database> <Dump_dir> <Dest_Dir> <Dest_file>

function _do_store_archive {

mkdir -p $3

cd $2

tar -cvzf $3/$4 dump

}

# _do_backup <Database name>

function _do_backup {

UNIQ_DIR="$BACKUP_TMP/$1"date “+%s”``

mkdir -p $UNIQ_DIR/dump

echo "dumping Mongo Database $1"

if [ "all" = "$1" ]; then

$MONGODUMP_BIN -o $UNIQ_DIR/dump

else

$MONGODUMP_BIN -d $1 -o $UNIQ_DIR/dump

fi

KEY="database-$BACKUPFILE_DATE.tgz"

echo "Archiving Mongo database to $BACKUP_DEST/$1/$KEY"

DEST_DIR=$BACKUP_DEST/$1

_do_store_archive $1 $UNIQ_DIR $DEST_DIR $KEY

rm -rf $UNIQ_DIR

}

# check to see if individual databases have been specified, otherwise backup the whole server

# to "all"

if [ "" = "$MONGO_DBS" ]; then

MONGO_DB="all"

_do_backup $MONGO_DB

else

for MONGO_DB in $MONGO_DBS; do

_do_backup $MONGO_DB

done

fi

9-1 列出了您必须更改的变量,以使这个简单的备份脚本适用于您的系统。

表 9-1。

The Variables Used in the Local Datastore Backup Script

| 可变的 | 描述 | | --- | --- | | `MONGO_DBS` | 将此变量留空("")可备份本地服务器上的所有数据库。或者您可以将数据库列表放入其中,以备份选定的数据库(`"db1 db2 db3"`)。 | | `BACKUP_TMP` | 将此变量设置为适合保存备份转储文件的临时目录。创建归档文件后,该目录中使用的临时数据将被删除。请务必选择一个与使用您的脚本相关的合适目录。例如,如果您使用脚本在本地帐户中创建备份,请使用`∼/tmp`;如果您将它用作在系统帐户下运行的系统 cronjob,请使用`/tmp`。在 Amazon EC2 实例上,您可能应该使用`/mnt/tmp`,这样文件夹就不会创建在系统根分区上,这个分区非常小。 | | `BACKUP_DEST` | 此变量保存备份的目标文件夹,并将在此文件夹下创建单独的文件夹。同样,将该目录放在与您使用备份脚本的方式相关的位置。 | | `MONGODUMP_BIN` | 因为您的备份脚本可能在没有设置完整路径集的帐户下运行,所以使用该变量指定该二进制文件的完整路径是明智的。您可以通过在终端窗口中键入`which mongodump`来确定系统上的适当路径。 | | `TAR_BIN` | 使用这个变量来设置 tar 二进制文件的完整路径;在终端窗口中使用`which tar`来确定该路径。 |

您现在可以使用此脚本来备份您的数据库;这样做将在指定的BACKUP_DEST directory创建一组归档备份。创建的文件遵循以下命名格式:

Database Name``/``database-YYYYMMDD-HHMM

例如,以下代码片段显示了本章测试数据库的备份名称:

Backups:$ tree

.

|-- blog

| |-- database-20100611-1144.tgz

| – database-20100611-1145.tgz`

``-- all`

|-- database-20100611-1210.tgz

|-- database-20100611-1221.tgz

|-- database-20100611-1222.tgz

|-- database-20100611-1224.tgz

``-- database-20100611-1233.tgz`

当然,你还需要安装脚本。如果您想每天运行这个脚本,只需将它放入/etc/cron.daily并重启cron服务以激活它。这种方法适用于大多数 Linux 发行版,比如 Ubuntu、Fedora、CentOS 和 RedHat。如果您想要不太频繁的备份,只需将脚本移动到/etc/cron.weekly/etc/cron.monthly。对于更频繁的备份,您可以使用/etc/cron.hourly

使用远程(基于云的)数据存储

上一节描述的脚本有一个创建和存储归档文件的独立函数。这使得修改脚本变得相对容易,以便它使用外部数据存储来存储备份归档。表 9-2 提供了几个例子,但是更多的其他机制也是可能的。

表 9-2。

Remote (Cloud-Based) Backup Storage Options

| 方法 | 描述 | | --- | --- | | rsync/ftp/tftp 或 scp 到另一台服务器 | 您可以使用 rsync 将归档文件移动到备份存储机器上。 | | s3 存储 | 如果你在 EC2 上运行你的系统,S3 存储是一个放置备份的好地方,因为存储成本很低,而且亚马逊会制作冗余副本。 |

我们将检查存储备份的 S3 方法;然而,同样的原则适用于任何其他机制。

这个例子使用了 http://s3tools.org 中的s3cmd实用程序(用 python 编写)。在 Ubuntu 上,你可以使用sudo apt-get install s3cmd命令安装这个脚本;在 Mac OSX 上,这个脚本可以从MacPorts集合中获得。在 Fedora、CentOS、RedHat 上,可以从 http://s3tools.org 获取yum包,然后使用yum安装。

一旦你安装了这个包,运行s3cmd –configure来设置你的亚马逊 S3 凭证。注意,你只需要提供两个键:AWS_ACCESS_KEYAWS_SECRET_ACCESS_KEYs3cmd实用程序将创建一个配置文件,该文件包含您需要的信息:∼/.s3cfg

以下是您需要对备份脚本进行的更改,以便与 S3 一起使用:

# _do_store_archive <Database> <Dump_dir> <Dest_Dir> <Dest_file>

BACKUP_S3_CONFIG=∼/.s3cfg

BACKUP_S3_BUCKET=somecompany.somebucket

S3CMD_BIN=/usr/bin/s3cmd

function _do_store_archive {

UNIQ_FILE="aws"date “+%s”``

cd $2

tar -cvzf $BACKUP_TMP/$UNIQ_FILE dump

$S3CMD_BIN --config $BACKUP_S3_CONFIG put $BACKUP_TMP/$UNIQ_FILE \

s3://$BACKUP_S3_BUCKET/$1/$4

rm -f $BACKUP_TMP/$UNIQ_FILE

}

9-3 列出了您需要配置的一些变量,以使这个改编的脚本工作。

表 9-3。

Configuring the Variables of Your Adapted Backup Script

| 可变的 | 描述 | | --- | --- | | `BACKUP_S3_CONFIG` | 运行`s3cmd –configure`以保存您的 S3 帐户详细信息时创建的`s3cmd`配置文件的路径。 | | `BACKUP_S3_BUCKET` | 希望脚本存储备份的存储桶的名称。 | | `S3CMD_BIN` | 到`s3cmd`可执行程序的路径,再次使用`which s3cmd`在您的系统上找到它。 |

备份大型数据库

使用大型数据库系统时,创建有效的备份解决方案可能会成为一个问题。通常,制作数据库副本所花费的时间很长;甚至可能需要几个小时才能完成。在此期间,您必须将数据库保持在一致的状态,这样备份就不会包含在不同时间点复制的文件。数据库备份系统的圣杯是时间点快照,它可以很快完成。快照完成得越快,数据库服务器必须冻结的时间窗口就越小。

使用隐藏的辅助服务器进行备份

用于执行大型备份的一种技术是从隐藏的辅助节点进行备份,该辅助节点可以在备份时被冻结。备份完成后,该辅助服务器将重新启动以跟上应用。

MongoDB 使用 MongoDB 的复制机制,使得设置隐藏的辅助服务器并让它跟踪主服务器变得非常简单。这也相对容易配置(关于如何设置隐藏的二级设备的更多细节,参见第 11 章)。

使用日志文件系统创建快照

许多现代卷管理器能够创建驱动器在任何特定时间点的状态快照。使用文件系统快照是创建 MongoDB 实例备份的最快、最有效的方法之一。虽然设置这些系统超出了本书的范围,但是我们可以向您展示如何将 MongoDB 服务器置于这样一种状态,即它的所有数据在磁盘上都处于一致的状态。我们还向您展示了如何阻止写入,以便进一步的更改不会写入磁盘,而是缓冲在内存中。

快照允许您准确读取拍摄快照时的驱动器。系统的卷或文件系统管理器确保在拍摄快照后磁盘上发生更改的任何数据块不会被写回到驱动器上的同一位置;这将保留磁盘上所有要读取的数据。通常,使用快照的过程是这样的:

Create a snapshot.   Copy data from the snapshot or restore the snapshot to another volume, depending on your volume manager.   Release the snapshot; doing so releases all preserved disk blocks that are no longer needed back into the free space chain on the drive.   Back up the data from the copied data while the server is still running.

刚才描述的方法的优点是,在拍摄快照时,对数据的读取可以不受阻碍地继续。

具有此功能的一些卷管理器包括:

  • Linux 和 LVM 卷管理系统
  • Sun ZFS
  • 亚马逊 EBS 卷
  • 使用卷影副本的 Windows Server

这些卷管理器中的大多数都能够在非常短的时间内(通常只有几秒钟)执行快照,即使数据量非常大也是如此。此时,卷管理器实际上并不复制数据;相反,它们实际上是在驱动器上插入一个书签,这样您就可以读取拍摄快照时驱动器的状态。

一旦备份系统从快照中读取了驱动器,那么随后被改变的旧块可以被释放回驱动器的自由空间链(或者文件系统用来标记自由空间的任何机制)。

为了使这成为创建备份的有效方法,我们必须让 MongoDB 日志文件存在于同一设备上,或者让 MongoDB 将所有未完成的磁盘写入刷新到磁盘,以便我们可以拍摄快照。强制 MongoDB 进行这种刷新的特性称为 fsync 阻止进一步写入的功能称为锁。MongoDB 能够同时执行这两种操作,因此在 fsync 之后,在释放锁之前,不会对磁盘进行进一步的写操作。通过将日志放在同一个设备上或执行 fsync 和 lock,我们可以使数据库在磁盘上的映像保持一致,并确保它在我们完成快照之前保持一致。

使用以下命令使 MongoDB 进入 fsync 和锁定状态:

$mongo

>use admin

>db.fsyncLock()

{

"info" : "now locked against writes"

"ok" : 1

}

您可以使用以下命令来检查锁的当前状态:

$mongo

>use admin

>db.currentOp()

{

"inprog" : [

]

"fsyncLock" : 1

}

"fsyncLock": 1状态表示 MongoDB 的 fsync 进程(负责将更改写入磁盘)当前被阻止执行写操作。

此时,您可以发出任何命令,让您的卷管理器创建 MongoDB 存储其数据文件的文件夹的快照。快照完成后,您可以使用以下命令来释放锁定:

$mongo

>db.fsyncUnlock();

{ "ok" : 1, "info" : "unlock requested" }

请注意,在释放锁之前可能会有一个小的延迟;但是,您可以使用db.currentOp()功能来检查结果。

当锁最终被清除时,db.currentOp()将返回以下内容:

$mongo

>use admin

>db.currentOp()

{ "inprog" : [] }

{ "inprog" : [] }行意味着锁已经被释放,MongoDB 可以再次开始写入磁盘。

现在您已经插入了快照书签,您可以使用与卷管理器相关的实用程序将快照的内容复制到合适的位置,以便存储备份。备份完成后,不要忘记释放快照。

有关快照的更多信息,请访问以下链接:

用于卷管理器的磁盘布局

一些卷管理器可以对一个分区上的子目录进行快照,但大多数不能,所以最好将计划用来存储 MongoDB 数据的卷挂载到文件系统上的一个合适的位置(例如,/mnt/mongodb),并使用服务器配置选项将数据目录、配置文件和任何其他与 MongoDB 相关的文件(例如,journal)单独放在这个挂载上。

这意味着当您拍摄卷的快照时,您捕获了服务器的完整状态,包括其配置。将服务器发行版的二进制文件直接放在该卷上可能是个好主意,这样您的备份就包含了一组完全协调的组件。

将数据导入 MongoDB

有时,您需要将大量数据加载到 MongoDB 中,作为参考数据使用。这些数据可能包括邮政编码表、ip 地理位置表、零件目录等。

MongoDB 包括一个批量“加载器”mongoimport,用于将数据直接导入服务器上的特定集合;这与mongorestore不同,后者设计用于从备份中恢复 MongoDB 二进制文件。

mongoimport实用程序可以从三种文件格式中的任何一种加载数据:

CSV: In this file format, each line represents a document, and fields are separated by commas.   TSV: This file format is similar to CSV; however, it uses a tab character as the delimiter. This format is popular because it does not require the escaping of any text characters other than those for new lines.   JSON: This format contains one block of JSON per line that represents a document. Unlike the other formats, JSON can support documents with variable schemas.

这个工具的使用相当直观。对于输入,它接受三种格式之一的文件、一个字符串或一个带有一组列标题名的文件(它们构成了 MongoDB 文档中的元素名),以及几个用于控制数据解释方式的选项。图 9-3 显示了如何使用mongoimport实用程序。

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

图 9-3。

The mongoimport help display showing its options

以下选项值得进一步解释:

  • --headerline:使用文件的第一行作为字段名列表。请注意,这仅适用于 CSV 和 TSV 格式。
  • --ignoreblanks:不导入空字段。如果字段为空,则不会在文档中为该行创建相应的元素;如果不调用这个选项,那么就会创建一个列名为的空元素。
  • --drop:删除一个集合,然后仅使用此次导入的数据重新创建它;否则,数据将被追加到集合中。

当使用mongoimport通过-d-c选项导入数据时,还必须指定数据库名称和集合名称,如下例所示:

$mongoimport -d blog -c tagcloud --type csv --headerline < csvimportfile.csv

从 MongoDB 导出数据

mongoexport实用程序类似于mongoimport,但是mongoexport,顾名思义,从现有的 MongoDB 集合创建导出文件。这是从 MongoDB 实例中以其他数据库或电子表格应用可以读取的格式提取数据的最佳方式之一。图 9-4 显示了如何使用mongoexport实用程序。

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

图 9-4。

The mongoexport help display showing its options

mongoexport实用程序中值得注意的选项包括:

  • -q:指定用于定位要输出的记录的查询。这个查询可以是任何 JSON 查询字符串(但不是 JavaScript 查询字符串,因为它通常不像预期的那样工作),您可以使用它和db.collection.find()函数来选择记录的子集。如果您没有指定这个选项或者您将它设置为{},那么mongoexport实用程序将输出所有记录。
    • f:列出要导出的数据库元素名称。

以下示例说明了如何使用mongoexport实用程序的选项:

$mongoexport -d blog -c posts -q {} -f _id,Title,Message,Author --csv >blogposts.csv

connected to: 127.0.0.1

exported 1 records

通过限制对 MongoDB 服务器的访问来保护数据

在某些情况下,您的应用可能会处理敏感数据,例如社交网络中的用户记录或电子商务应用中的支付细节。在许多情况下,有规则要求您必须确保对数据库系统中敏感数据的受限访问。

MongoDB 支持一个简单的基于角色的认证系统,该系统允许您控制谁有权访问每个数据库,以及他们被授予的访问级别。

在 MongoDB 服务器上,大多数更改数据配置或对其结构进行重大修改的命令都被限制为只能在每次新安装 MongoDB 时自动创建的特殊的admin数据库中运行。

在发出这些命令之前,您必须使用use admin命令切换到admin数据库。接下来的章节将会提到任何只允许管理员使用的命令,所以在你使用它之前,你总是知道你什么时候需要在admin数据库中。本章假设您可以选择数据库,并在必要时对其进行验证。

默认情况下,MongoDB 不使用任何身份验证方法。任何能够访问网络连接的人都可以连接到服务器并向其发出命令。但是,您可以向任何数据库添加用户,并且 MongoDB 可以配置为要求连接和控制台身份验证来访问相关的数据库。这是限制访问管理功能的推荐机制。

通过身份验证保护您的服务器

MongoDB 支持一个简单的身份验证模型,该模型允许管理员在每个用户的基础上限制对数据库的访问。

MongoDB 支持每个数据库上的单独访问控制记录;这些记录存储在一个特殊的system.users集合中。对于能够访问两个数据库(例如,db1db2)的普通用户,他们的凭证和权限必须添加到这两个数据库中。

如果您为同一用户在不同的数据库上创建单独的登录和访问权限,则这些记录之间不会同步。换句话说,更改一个数据库上的用户密码不会更改任何其他数据库上的密码。然而,MongoDB 团队在 2.4 版本中引入了一种新的机制来允许委托凭证。以这种方式使用这些凭证,您可以创建一个带有密码的主用户。然后在其他数据库上创建用户,并指定该用户已经存在于 master 数据库中,并且应该使用其凭据进行身份验证。

这条规则还有一个最后的(也是关键的)例外:任何添加到特殊的admin数据库的用户将对所有数据库拥有相同的访问权限;您不需要为这些用户单独分配权限。

Note

如果您在添加 admin 用户之前启用了身份验证,那么您将只能通过 localhost 访问您的数据库,这意味着从托管 MongoDB 实例的机器建立连接。这是一项安全功能,旨在允许管理员在启用身份验证后创建用户。

添加管理员用户

添加admin用户就像切换到admin数据库并使用addUser()函数一样简单:

$mongo

> use admin

> db.addUser({user : "admin", pwd: "pass", roles: [ "readWrite", "dbAdmin" ] })

{

"user" : "admin"

"pwd" : "e4e538f5dcb52537cad02bbf8491693c"

"roles" : [

"readWrite"

"dbAdmin"

]

"_id" : ObjectId("5239915b1ce3dc1efebb3c84")

}

此时,您只需要添加一个admin用户;一旦定义了该用户,就可以使用它将其他的admin用户添加到admin数据库中,或者将普通用户添加到任何其他数据库中。

启用身份验证

现在,您需要更改服务器的配置来启用身份验证。为此,请停止您的服务器,并将--auth添加到启动参数中。

如果你用打包的安装程序安装了 MongoDB,比如yum或者 Aptitude,那么通常你可以编辑/etc/mongodb.conf来启用auth=true。接下来,您可以使用以下命令重新启动服务器并启用身份验证:

$sudo service mongodb restart

除了auth之外,您还可以使用 keyfile,这是一个包含某种描述的预共享密钥的文件,用于确认 MongoDB 节点之间的通信。要创建密钥文件,只需创建一个简单的文件,其中包含要使用的短语或字符串。然后像处理auth一样添加选项keyfile=/path/to/keyfile。你甚至可以删除旧的auth=true选项,因为用keyfile运行意味着auth

在 mongo 控制台中验证

在运行admin数据库中的受限命令之前,您需要被认证为admin用户,如下例所示:

$mongo

> use admin

switched to db admin

>show collections

Sun May 26 17:22:26.132 JavaScript execution failed: error: {

"$err" : "not authorized for query on admin.system.namespaces"

"code" : 16550

} at src/mongo/shell/query.js:L131 }

>db.auth("admin", "pass");

1

此时,mongo控制台会打印出1(认证成功)或0(认证失败):

1

>show collections

system.indexes

system.users

如果您的身份验证成功,您将能够根据您的用户权限执行任何可用的操作。

如果您的身份验证不成功,那么您需要检查您的用户名/密码是否正确,以及admin用户是否已正确添加到admin数据库中。重置您的服务器,使其没有身份验证,然后使用以下命令列出admin数据库中system.users集合的内容:

$mongo

>use admin

> db.system.users.find()

{ "_id" : ObjectId("5239915b1ce3dc1efebb3c84"), "user" : "admin", "pwd" : "e4e538f5dcb52537cad02bbf8491693c", "roles" : [ "readWrite", "dbAdmin" ] }

Note

如果您使用一个admin凭证来访问除了admin之外的数据库,那么您必须首先通过admin数据库的认证。否则,您将无法访问系统中的任何其他数据库。

mongo控制台显示user集合的内容,使您能够看到什么是userid,而密码显示为您提供的原始密码的 MD5 散列:

$ mongo

> use blog

switched to db blog

> show collections

Wed Sep 18 21:42:51.855 JavaScript execution failed: error: {

"$err" : "not authorized for query on blog.system.namespaces"

"code" : 13

} at src/mongo/shell/query.js:L128

> db.auth("admin","pass")

Error: 18 { code: 18, ok: 0.0, errmsg: "auth fails" }

0

> use admin

switched to db admin

> db.auth("admin","pass")

1

> use blog

switched to db blog

> show collections

system.indexes

system.users

authors

posts

tagcloud

MongoDB 用户角色

目前,MongoDB 支持用户在其权限框架内可以拥有的以下角色:

  • 读取—允许用户从给定的数据库中读取。
  • readWrite 授予用户对给定数据库的读写权限。
  • db admin—允许用户在给定的数据库中执行管理功能,例如创建或删除索引、查看统计数据或访问system.profile集合。
  • user admin—允许用户写入system.users集合。有了该权限,您可以创建、删除和管理该数据库的用户。
  • cluster admin—仅在admin数据库中可用。授予对所有与分片和副本集相关的功能的完全管理权限。
  • readany database—仅在admin数据库中可用。授予对所有数据库的读取权限。
  • readWriteAnyDatabase—仅在admin数据库中可用。授予对所有数据库的读写权限。
  • useradminany database—仅在admin数据库中可用。授予 userAdmin 对所有数据库的权限。
  • dbAdminAnyDatabase—仅在admin数据库中可用。授予 dbAdmin 对所有数据库的权限。

委派凭据

如前所述,从 MongoDB 2.4 版本开始,可以拥有一个主用户,然后创建后续用户,这些用户使用主用户的凭证进行身份验证,这一特性称为创建委托凭证。假设我们在我们的foo数据库上创建用户tes t,如下所示:

> use foo

> db.addUser(user : "test", pwd: "password", roles: ["readWrite" ])

现在,假设我们想要在bar数据库上创建相同的测试用户。我们可以运行下面的命令(当然,作为一个拥有该数据库的用户管理员权限的用户)来创建我们的test用户,该用户将使用 foo 数据库的定义作为其密码:

>use bar

> db.system.users.insert{ user: "test", roles: ["read"], userSource: "foo"}

请注意,该用户仅被授予只读权限“read”这是因为授予 bar 上的测试用户的访问权限仍然基于这个 bar 用户的凭证。我们只是从foo数据库中获取我们需要的其余细节(即密码)。通过使用委派凭据,您可以创建一个单一位置来更新所有用户的用户名和密码。

更改用户的凭据

更改用户的访问权限或密码很容易。通过再次执行addUser()函数可以做到这一点,这将导致 MongoDB 更新现有的用户记录。从技术上讲,您可以使用任何普通的数据操作命令来更改用户的记录;但是,只有addUser()函数可以创建密码字段。

无论如何,您可以通过列出其内容来了解addUser()是如何工作的:

$mongo

>use admin

> db.addUser

function () {

if (arguments.length == 0) {

throw Error("No arguments provided to addUser");

}

if (typeof arguments[0] == "object") {

this._addUser.apply(this, arguments);

} else {

this._addUserV22.apply(this, arguments);

}

}

addUser()只是 JavaScript 中定义的一个函数。如果您想创建一个允许您向数据库添加用户的 web 表单,或者想从另一个凭证源将用户全部导入到系统中,了解密码的构造方式是非常有用的。

大多数mongo控制台功能可以以这种方式列出,使您能够检查它们如何工作的细节。

添加只读用户

addUser()函数包括一个附加参数,允许您创建一个只有只读权限的用户。如果作为新创建的用户进行身份验证的进程试图做任何会导致数据库内容发生变化的事情,MongoDB 客户端将抛出异常。以下示例为用户提供了对数据库的访问权限,以便进行状态监控或报告:

$mongo

>use admin

switched to db admin

>db.addUser(user : "admin", pwd: "pass", roles: [ "read" ])

1

>use blog

switched to db blog

>db.addUser("shadycharacter","shadypassword", true)

删除用户

要从数据库中删除用户,只需对集合使用普通的remove()函数。以下示例删除刚刚添加的用户;请注意,在删除用户之前,您必须针对admin数据库进行身份验证:

$mongo

>use admin

switched to db admin

> db.auth("admin","pass")

1

>use blog

switched to db blog

>db.removeUser("shadycharacter")

在 PHP 应用中使用认证连接

在第 4 章中,您看到了如何用 PHP 创建到 MongoDB 服务器的连接。一旦在服务器上启用了身份验证,PHP 应用也必须提供凭证,然后才能对服务器执行命令。以下简单示例显示了如何打开到数据库的已验证连接:

<?php

// Establish the database connection

$connection = new Mongo();

$db = $connection->selectDB(“admin”);

$result = $db->authenticate(“admin”, “pass”);

if(!$result[‘ok’]){

// Your Error handling here

die(“Authentication Error: {$result[‘errmsg’]}”);

}

// Your code here

// Close the database connection

$connection->close();

?>

管理服务器

作为管理员,您必须确保 MongoDB 服务器平稳可靠地运行。

您必须定期调整服务器以获得最佳性能,或者重新配置它们以更好地适应您的操作环境。为此,您需要熟悉一些使您能够管理和控制服务器的过程。

启动服务器

大多数现代 Linux 发行版现在都包括一组用于管理服务的/etc/init.d脚本。如果您使用 MongoDB 站点上的一个分发包安装了您的 MongoDB 服务器(参见第 2 章中关于这些包的更多信息),那么用于管理您的服务器的init.d脚本已经安装好了。

您可以在 Ubuntu、Fedora、CentOS 和 RedHat 上使用service命令来启动、停止和重启服务器,如下例所示:

$sudo service mongodb start

mongodb start/running, process 3474

$sudo service mongodb stop

mongodb stop/waiting

$sudo service mongodb restart

mongodb start/running, process 3474

如果没有可用的初始化脚本,可以通过打开终端窗口,然后键入以下命令来手动启动 MongoDB 服务器:

$ mongod

Fri May 24 15:06:20.475 [initandlisten] MongoDB starting : pid=97163 port=27017 dbpath=/var/lib/mongodb 64-bit host=Pixl.local

Fri May 24 15:06:20.475 [initandlisten] db version v2.5.1-pre

Fri May 24 15:06:20.475 [initandlisten] git version: 704dc4fdf5248077c53271f249260478d6c56cd3

Fri May 24 15:06:20.475 [initandlisten] build info: Darwin bs-osx-106-x86-64-1.local 10.8.0 Darwin Kernel Version 10.8.0: Tue Jun 7 16:33:36 PDT 2011; root:xnu-1504.15.3∼1/RELEASE_I386 i386 BOOST_LIB_VERSION=1_49

Fri May 24 15:06:20.475 [initandlisten] allocator: system

Fri May 24 15:06:20.475 [initandlisten] options: {}

Fri May 24 15:06:20.479 [initandlisten] journal dir=/data/db/journal

Fri May 24 15:06:20.479 [initandlisten] recover : no journal files present, no recovery needed

Fri May 24 15:06:20.547 [websvr] admin web console waiting for connections on port 28017

服务器将显示所有正在进行的连接,以及其他可以用来监控服务器工作情况的信息。

要以手动模式终止服务器,只需键入^C;这将导致服务器彻底关闭。

如果您不提供配置文件,那么 MongoDB 将使用默认数据库路径/data/db启动,并使用默认端口 27017 (mongodb)和 28017(管理界面)绑定到所有网络 IP,如下例所示:

$ mkdir -p /data/db

$ mongod

mongod --help for help and startup options

...

Sun Jun 13 13:38:00 waiting for connections on port 27017

Sun Jun 13 13:38:00 web admin interface listening on port 28017

^C

Sun Jun 13 13:40:26 got kill or ctrl c signal 2 (Interrupt), will terminate after current cmd ends

...

Sun Jun 13 13:40:26 dbexit: really exiting now

重新配置服务器

MongoDB 提供了三种配置服务器的主要方法。首先,您可以将命令行选项与mongod服务器守护进程结合使用。其次,您可以通过加载配置文件来实现。第三,您可以使用setParameter命令更改大多数设置。例如,我们可以用下面的命令将logLevel改回默认值 0:

> db.adminCommand( {setParameter:1, logLevel:0 } )

大多数预打包的 MongoDB 安装程序使用后一种方法,使用通常存储在 Unix/Linux 系统上的/etc/mongodb.conf中的文件。

您可以通过编辑该文件并重新启动服务器来更改服务器的配置。该文件的内容如下所示:

# mongodb.conf

dbpath=/var/lib/mongodb

logpath=/var/log/mongodb/mongodb.log

logappend=true

auth = false

#enable the rest interface

rest =true

您可以通过删除选项前面的#代码并根据需要设置其值来启用选项,因为任何以#开头的行都被认为是“注释”,因此会被忽略。

在配置文件中放置以下任何选项值都等同于指定

--<optionname> <optionvalue>

启动 MongoDB 时在命令行上:

  • dbpath:表示 MongoDB 将存储您的数据的位置;您应该确保它位于足够大的快速存储卷上,以支持您的数据库大小。
  • logpath:表示 MongoDB 将在其中存储日志的文件。放这个的标准地方是/var/logs/mongodb/mongodb.log;你需要使用logrotate来旋转这个日志文件,防止它填满你的服务器驱动器。
  • logappend:将该选项设置为false会导致每次启动 MongoDB 时清除日志文件。将此选项设置为true会将所有日志条目附加到任何现有日志文件的末尾。
  • auth:启用或禁用 MongoDB 服务器上的认证模式;有关身份验证的更多信息,请参见本章前面的讨论。
  • rest:启用或禁用 MongoDB 的rest接口。如果您想使用基于 web 的状态显示中的链接来显示附加信息,那么您必须启用这个接口,但是不建议生产服务器使用这个接口,因为所有这些信息都应该可以通过 Mongo shell 获得。

获取服务器的版本

你可以用数据库。version()获取服务器内部版本和版本信息的函数。此信息对于确定是否需要升级或向支持论坛报告问题非常有用。以下代码片段显示了如何使用该命令:

$mongo

> use admin

switched to db admin

> db.version()

version: 2.5.1-pre-

获取服务器的状态

MongoDB 提供了一种简单的方法来确定服务器的状态。

Note

请记住,如果您正在使用auth,您的用户将需要权限来运行这些命令。

以下示例显示了返回的信息,包括服务器正常运行时间、最大连接数等信息:

$mongo

> db.serverStatus()

{

"host" : "Pixl.local"

"version" : "2.5.1-pre-"

"process" : "mongod"

"pid" : 3737

"uptime" : 44

"uptimeMillis" : NumberLong(43035)

"uptimeEstimate" : 39

"localTime" : ISODate("2013-05-25T12:38:34.015Z")

"asserts" : {

"regular" : 0

"warning" : 0

"msg" : 0

"user" : 1

"rollovers" : 0

}

"connections" : {

"current" : 1

"available" : 2047

"totalCreated" : NumberLong(1)

}

"cursors" : {

"totalOpen" : 0

"clientCursors_size" : 0

"timedOut" : 0

}

"globalLock" : {

"totalTime" : NumberLong(43035000)

"lockTime" : NumberLong(48184)

"currentQueue" : {

"total" : 0

"readers" : 0

"writers" : 0

}

}

"locks" : {

"admin" : {

"timeLockedMicros" : {

"r" : NumberLong(54)

"w" : NumberLong(0)

}

"timeAcquiringMicros" : {

"r" : NumberLong(2190)

"w" : NumberLong(0)

}

}

"local" : {

"timeLockedMicros" : {

"r" : NumberLong(45)

"w" : NumberLong(6)

}

"timeAcquiringMicros" : {

"r" : NumberLong(7)

"w" : NumberLong(1)

}

}

...

}

"network" : {

"bytesIn" : 437

"bytesOut" : 6850

"numRequests" : 7

}

"opcounters" : {

"insert" : 1

"query" : 6

"update" : 0

"delete" : 0

"getmore" : 0

"command" : 7

}

...

"mem" : {

"bits" : 64

"resident" : 37

"virtual" : 3109

"supported" : true

"mapped" : 320

"mappedWithJournal" : 640

}

"ttl" : {

"deletedDocuments" : NumberLong(0)

"passes" : NumberLong(0)

}

}

"ok" : 1

}

正如你所看到的,serverStatus输出了相当多的细节,上面是被截断的!您可以在opcountersasserts部分找到该函数返回的信息中最重要的两个部分。

opcounters部分显示了针对数据库服务器执行的每种操作的数量。对于您的特定应用,您应该很清楚这些计数器的正常余额是由什么组成的。如果这些计数器开始超出正常比率,那么这可能是您的应用有问题的早期警告。

例如,所示的概要文件具有极高的插入/读取比率。这对于日志记录应用来说是正常的;然而,对于一个博客应用,它可能表明要么是一个垃圾邮件正在点击您的“评论”部分,要么是一个导致数据库写入的 URL 模式正在被搜索引擎蜘蛛重复抓取。在这种情况下,是时候在你的评论表单上放一个验证码,或者在你的robots.tx file中屏蔽特定的 URL 模式了。

asserts部分显示了已经抛出的服务器和客户机异常或警告的数量。如果这样的异常或警告开始迅速增加,那么是时候好好检查一下服务器的日志文件,看看问题是否正在发展。大量的断言也可能表明数据库中的数据有问题,您应该检查 MongoDB 实例的日志文件,以确认这些断言的性质,以及它们是否表明正常的“用户断言”,这表示类似重复键违规或更紧迫的问题。

关闭服务器

如果您已经从一个包中安装了 MongoDB 服务器,那么您可以使用操作系统的服务管理脚本来关闭服务器。例如,Ubuntu、Fedora、CentOS 和 RedHat 允许您通过发出以下命令来关闭服务器:

$sudo service mongod stop

您也可以从mongo控制台关闭服务器:

$mongo

>use admin

>db.shutdownServer()

您可以使用 Posix 进程管理命令来终止服务器,或者使用SIG_TERM(-15) or SIG_INT(-2)信号来关闭服务器。

如果且仅当服务器无法响应这两种方法时,您可以使用以下命令:

$sudo killall -15 mongod

Warning

您不能使用SIG_KILL(-9)信号来终止服务器,因为这可能会导致数据库损坏,并且您可能需要修复服务器。

这可能是因为您有一个特别活跃的服务器,它有很多写入活动,并且您已经重新配置了该服务器,因此它有很大的同步延迟。如果是这种情况,那么服务器可能不会立即响应终止请求,因为它正在将所有内存中的更改写到磁盘上。一点点耐心在这里大有帮助。

使用 MongoDB 日志文件

默认情况下,MongoDB 将其整个日志输出写入stdout;但是,您可以使用前面描述的logpath选项将日志输出重定向到一个文件。

您可以使用日志文件的内容来发现问题,例如来自单个计算机的过多连接以及其他可能表明应用逻辑或数据有问题的错误消息。

验证和修复您的数据

如果您的服务器意外重启或者您的 MongoDB 服务器由于任何原因崩溃,您的数据可能会处于损坏或不完整的状态。

以下是一些表明您的数据已经受损的迹象:

  • 您的数据库服务器拒绝启动,声称数据文件已损坏。
  • 您开始在服务器日志文件中看到断言,或者在使用db.serverStatus()命令时看到高断言计数。
  • 您从查询中得到奇怪或意想不到的结果。
  • 收藏数量的记录与你的期望不符。

这些迹象中的任何一个都可能表明您的应用有问题,或者更令人担忧的是,您的数据损坏或不一致。

幸运的是,MongoDB 附带了帮助您修复或恢复数据库服务器的工具。尽管如此,您仍然可能会丢失一些数据,所以请记住黄金法则,确保您有一个良好的数据备份或一个复制从属。

修复服务器

在您启动服务器修复过程之前,您必须意识到运行repair命令是一项成本高昂的操作,可能会花费大量时间,并且它需要的空间是 MongoDB 数据文件所占用空间的两倍,因为所有数据都被克隆到新文件中并完全重新创建,这实际上是所有数据文件的重建。这是使用副本集的最佳论据之一:如果您必须让一台机器脱机进行修复,您不必完全停止副本集为您的客户机提供服务。

要启动修复过程,只需使用手动服务器启动过程(如本章前面所述)。但是,这一次您需要将--repair选项添加到命令的末尾,如下例所示:

$ mongod --dbpath /data/db --repair

Wed Sep 18 21:21:21.364 [initandlisten] MongoDB starting : pid=5973 port=27017 dbpath=/data/db 64-bit host=Pixl.local

Wed Sep 18 21:21:21.364 [initandlisten]

Wed Sep 18 21:21:21.364 [initandlisten] ** WARNING: soft rlimits too low. Number of files is 256, should be at least 1000

Wed Sep 18 21:21:21.364 [initandlisten] db version 2.5.1-pre-

Wed Sep 18 21:21:21.364 [initandlisten] git version: 704dc4fdf5248077c53271f249260478d6c56cd3

Wed Sep 18 21:21:21.364 [initandlisten] build info: Darwin bs-osx-106-x86-64-2.10gen.cc 10.8.0 Darwin Kernel Version 10.8.0: Tue Jun 7 16:32:41 PDT 2011; root:xnu-1504.15.3∼1/RELEASE_X86_64 x86_64 BOOST_LIB_VERSION=1_49

Wed Sep 18 21:21:21.364 [initandlisten] allocator: system

Wed Sep 18 21:21:21.364 [initandlisten] options: { dbpath: "/data/db", repair: true }

Wed Sep 18 21:21:21.367 [initandlisten] build index test.system.users { user: 1, userSource: 1 }

Wed Sep 18 21:21:21.368 [initandlisten] build index done. scanned 1 total records. 0.001 secs

Wed Sep 18 21:21:21.368 [initandlisten] ****

Wed Sep 18 21:21:21.368 [initandlisten] ****

Wed Sep 18 21:21:21.368 [initandlisten] need to upgrade database test with pdfile version 4.5, new version: 4.5

Wed Sep 18 21:21:21.368 [initandlisten] starting upgrade

Wed Sep 18 21:21:21.368 [initandlisten] test repairDatabase test

Wed Sep 18 21:21:21.369 [FileAllocator] allocating new datafile /data/db/_tmp_repairDatabase_0/test.ns, filling with zeroes...

Wed Sep 18 21:21:21.369 [FileAllocator] creating directory /data/db/_tmp_repairDatabase_0/_tmp

Wed Sep 18 21:21:21.389 [FileAllocator] done allocating datafile /data/db/_tmp_repairDatabase_0/test.ns, size: 16MB, took 0.02 secs

Wed Sep 18 21:21:21.389 [FileAllocator] allocating new datafile /data/db/_tmp_repairDatabase_0/test.0, filling with zeroes...

Wed Sep 18 21:21:21.583 [FileAllocator] done allocating datafile /data/db/_tmp_repairDatabase_0/test.0, size: 64MB, took 0.193 secs

Wed Sep 18 21:21:21.583 [FileAllocator] allocating new datafile /data/db/_tmp_repairDatabase_0/test.1, filling with zeroes...

Wed Sep 18 21:21:21.586 [initandlisten] build index test.foo { _id: 1 }

Wed Sep 18 21:21:21.661 [initandlisten] fastBuildIndex dupsToDrop:0

Wed Sep 18 21:21:21.661 [initandlisten] build index done. scanned 1 total records. 0.074 secs

Wed Sep 18 21:21:21.661 [initandlisten] build index test.system.users { user: 1, userSource: 1 }

Wed Sep 18 21:21:21.662 [initandlisten] fastBuildIndex dupsToDrop:0

Wed Sep 18 21:21:21.662 [initandlisten] build index done. scanned 0 total records. 0 secs

Wed Sep 18 21:21:21.662 [initandlisten] build index test.system.users { _id: 1 }

Wed Sep 18 21:21:21.663 [initandlisten] fastBuildIndex dupsToDrop:0

Wed Sep 18 21:21:21.663 [initandlisten] build index done. scanned 1 total records. 0 secs

Wed Sep 18 21:21:22.002 [FileAllocator] done allocating datafile /data/db/_tmp_repairDatabase_0/test.1, size: 128MB, took 0.418 secs

Wed Sep 18 21:21:22.018 [initandlisten] finished checking dbs

Wed Sep 18 21:21:22.018 dbexit:

Wed Sep 18 21:21:22.018 [initandlisten] shutdown: going to close listening sockets...

Wed Sep 18 21:21:22.018 [initandlisten] shutdown: going to flush diaglog...

Wed Sep 18 21:21:22.018 [initandlisten] shutdown: going to close sockets...

Wed Sep 18 21:21:22.018 [initandlisten] shutdown: waiting for fs preallocator...

Wed Sep 18 21:21:22.018 [initandlisten] shutdown: closing all files...

Wed Sep 18 21:21:22.018 [initandlisten] closeAllFiles() finished

Wed Sep 18 21:21:22.018 [initandlisten] shutdown: removing fs lock...

Wed Sep 18 21:21:22.018 dbexit: really exiting now

在本例中,repair检测到admin数据库可能是在旧版本的 MongoDB 下创建的,它需要升级其存储格式以匹配当前运行的服务器。

Note

运行带有--repair选项的mongod实用程序后,服务器退出是正常的;要使它重新联机,只需再次启动它,无需指定--repair选项。

修复过程完成后,您应该能够照常启动服务器,然后从备份中还原任何丢失的数据。

如果您试图修复一个大型数据库,您可能会发现您的驱动器耗尽了磁盘空间,因为 MongoDB 可能需要在与数据相同的驱动器上创建一个数据库文件的临时副本(参见前面示例中的.../$tmp_repairDatabase_0/..目录)。

为了克服这个潜在的问题,MongoDB 修复实用程序支持一个名为--repairpath的附加命令行参数。您可以使用此参数指定一个驱动器,该驱动器具有足够的空间来保存它在重建过程中创建的临时文件,如下例所示:

$ mongod -f /etc/mongodb.conf --repair --repairpath /mnt/bigdrive/tempdir

验证单个集合

有时,您可能会怀疑正在运行的服务器上的数据有问题。在这种情况下,您可以使用 MongoDB 附带的一些工具来帮助您确定有问题的服务器是否已经损坏。

您可以使用validate选项来验证数据库中集合的内容。下一个例子展示了如何对包含一百万条记录的集合运行validate选项:

$mongo

> use blog

switched to db blog

>db.posts.ensureIndex({Author:1})

> db.posts.validate()

{

"ns" : "blog.posts"

"firstExtent" : "0:f000 ns:blog.posts"

"lastExtent" : "0:2b9000 ns:blog.posts"

"extentCount" : 6

"datasize" : 6717520

"nrecords" : 29997

"lastExtentSize" : 8388608

"padding" : 1

"firstExtentDetails" : {

"loc" : "0:f000"

"xnext" : "0:11000"

"xprev" : "null"

"nsdiag" : "blog.posts"

"size" : 8192

"firstRecord" : "0:f0b0"

"lastRecord" : "0:10e50"

}

"lastExtentDetails" : {

"loc" : "0:2b9000"

"xnext" : "null"

"xprev" : "0:b9000"

"nsdiag" : "blog.posts"

"size" : 8388608

"firstRecord" : "0:2b90b0"

"lastRecord" : "0:6ec830"

}

"deletedCount" : 4

"deletedSize" : 3983552

"nIndexes" : 2

"keysPerIndex" : {

"blog.posts.$_id_" : 29997

"blog.posts.$Author_1" : 29997

}

"valid" : true

"errors" : [ ]

"warning" : "Some checks omitted for speed. use {full:true} option to do more thorough scan."

"ok" : 1

}

前面的示例大约需要 30 秒钟才能完成。默认情况下,validate选项检查数据文件和索引,并在收集完成时提供一些关于收集的统计信息。该选项会告诉您数据文件或索引是否有任何问题,但不会检查每个文档的正确性。如果检查每个文档是您想要的,那么您可以运行(如输出中所建议的)带有{full: true}选项的validate,这是通过向函数调用添加true参数来调用的,如下所示:db.posts.validate(true)

如果您有一个非常大的数据库,并且您只想验证索引,那么您也可以使用validate选项。在当前版本(2.6.0)中,没有用于此的 shell helper 命令。但是这并不是一个障碍,因为您可以使用runCommand选项轻松完成这个索引验证:

$mongo

>use blog

>db.runCommand({validate:"posts", scandata:false})

在这种情况下,服务器不会扫描数据文件;相反,它仅仅报告存储的关于集合的信息。

修复集合验证错误

如果在您的集合上运行验证发现了一个错误,这将在 validate 文档的errors部分中记录,您有几个选项来修复数据。同样,拥有良好备份的重要性怎么强调都不为过。在直接开始恢复您的备份之前,您应该查看 MongoDB 实例的日志,看看是否有关于错误性质的任何附加信息;如果是这样,这应该会告诉你下一步该怎么做。

修复集合的索引

如果验证过程显示索引被损坏,使用reIndex()函数重新索引受影响的集合。在下面的例子中,您使用reIndex()函数来重新索引博客的posts集合,之前您已经向该集合添加了author索引:

$mongo

>use blog

> db.posts.reIndex()

{

"nIndexesWas" : 2

"msg" : "indexes dropped for collection"

"nIndexes" : 2

"indexes" : [

{

"key" : {

"_id" : 1

}

"ns" : "blog.posts"

"name" : "_id_"

}

{

"key" : {

"Author" : 1

}

"ns" : "blog.posts"

"name" : "Author_1"

}

]

"ok" : 1

}

MongoDB 服务器将删除集合上的所有当前索引并重新构建它们;但是,如果您使用 database repair选项,它还会对数据库中的所有集合运行reIndex()函数。

修复集合的数据文件

修复数据库中所有数据文件的最佳——也是最危险的——方法是使用服务器的--repair选项或 db。repairDatabase()shell 中的命令。后者修复单个数据库中的所有集合文件,然后重新索引所有已定义的索引。然而,repairDatabase()并不是一个适合在实时服务器上运行的函数,因为它会在重建数据文件时阻止对数据的任何请求。这导致在数据库修复完成时阻止所有读取和写入。下面的代码片段显示了使用repairDatabase()函数的语法:

$mongo

>use blog

>db.repairDatabase()

{ "ok" : 1 }

Warning

MongoDB 的修复是一个蛮力选项。它试图修复和重建您的数据结构和索引。它通过尝试从磁盘读取并重建整个数据结构来实现这一点。如果可能,您应该尝试从备份中恢复;repairDatabase()只应作为最后手段使用。

压缩集合的数据文件

由于 MongoDB 在内部分配数据文件的方式,您可能会遇到俗称的“瑞士奶酪”,这意味着在磁盘数据结构中留下了数据存储空间的一小部分空白。这可能是一个问题,因为这意味着您的数据文件有很大一部分未被使用。虽然修复以重建整个数据结构可能会有所帮助,但可能会有其他意想不到的后果。compact命令将对现有数据文件中给定集合的数据结构进行分片整理和重组,但它不会恢复磁盘空间。

$mongo

>use blog

> db.runCommand({compact:"posts"})

{ "ok" : 1 }

升级 MongoDB

有时,新版本的 MongoDB 会要求您升级数据库文件的格式。MongoDB,Inc .的团队意识到了在正在运行的生产服务上运行升级所带来的影响(包括导致的停机时间);但是,有时为了支持大量需求的新功能,需要进行升级。

Warning

在尝试任何升级过程之前,对您的数据进行完整备份是非常重要的。除此之外,您应该经常查看发行说明,这些说明可从 http://docs.mongodb.org/manual/release-notes/ 获得。

MongoDB 的开发者试图预测升级过程中可能出现的每一个问题;然而,你也必须采取措施保护自己。升级通常会以新的格式重写系统中的每一条数据,这意味着即使是过程中最轻微的问题也会带来灾难性的后果。

以下列表指导您完成升级数据库服务器所需的正确步骤:

Back up your data and make sure that the backup is viable. If possible, restore the backup to another server and verify that it’s OK.   Stop your application or divert it to another server.   Stop your MongoDB server.   Upgrade the code of your MongoDB server to the desired version.   Use the shell to perform initial sanity checks on the data.   If anything looks suspicious, use the validation tools to check the data.   Re-enable your application when you are satisfied that everything looks OK.   Test your application carefully before reopening the service or diverting traffic back to this server.

MongoDB 的滚动升级

拥有副本集的一个重要特性是它可以用于执行滚动升级。这种方法旨在最大限度地减少像这样的大型变更所带来的潜在停机时间和影响。除了遵循下面概述的流程之外,您应该始终进行备份,并在您的非生产环境中进行测试。完成尽职调查以确保系统可恢复后,您可以遵循以下流程:

Stop and perform an upgrade for each secondary one at a time.   Run the rs.stepDown() command on the primary. One of the upgraded secondaries will step into place as primary.   Upgrade the primary.

监控 MongoDB

MongoDB 发行版包含一个名为mongostat的简单状态监控工具。该工具主要用于提供服务器上发生的事情的简单概述(见图 9-5 )。

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

图 9–5。

Monitoring the status of MongoDB with the mongostat utility

这个工具产生的统计数据并不广泛,但是它们确实提供了 MongoDB 安装中正在发生的事情的一个很好的概述。例如,这个显示可以让您看到数据库操作的执行频率、索引命中率,以及应用在等待释放数据库锁时被阻塞的时间。

感兴趣的主要列是前六列,它们显示了mongod服务器处理某些操作(例如,插入或查询)的速度。在诊断安装问题时,值得关注的其他列包括:

  • Pagefaults:表示当您的 MongoDB 实例需要从磁盘读取数据以完成查询时。这通常是次优性能的一个指标,日常操作通常需要的所有数据都无法保存在 MongoDB 可用的 RAM 中。您应该检查是否有任何可能扫描所有文档而不使用索引的查询,或者您可能需要转移到具有更多可用 RAM 的服务器。
  • queues:表示排队等待执行的操作数。由于 MongoDB 允许一个写者(插入、更新和删除)和许多读者(发现),这可能导致读查询被性能差的写阻塞的情况。更糟糕的是,您可能会遇到这样的情况:一个性能不佳的写操作阻塞了多个读写操作。检查哪些查询可能会阻止其他查询的执行。
  • % locked:显示给定集合的写锁被解除的时间百分比。此处非常高的数字表示您正在执行一个或多个几乎在整个时间窗口内运行的写操作。高的% locked可能会影响所有查询的性能。检查您的写入性能是否很差,或者您的系统是否存在页面错误,这可能表明需要更多的 RAM。这也可能与模式问题有关,比如文档中非常大的数组,请记住,在 MongoDB 2.2 之前,锁定是在每个实例级别上进行的,因此升级可能有助于通过最近的并发性改进来降低该值。

ROLLING YOUR OWN STAT MONITORING TOOL

mongostat提供的许多信息与您可以从db.serverStatus()呼叫中获得的信息相同。创建一个使用这个 API 每隔几秒钟轮询一次服务器,然后将结果放入 MongoDB 集合的服务并不是一件难事。

一些索引、一些精心制作的查询和一个图形包的应用将使您能够使用这样一个简单的实时监控器来生成历史日志。

MongoDB 还提供了许多第三方适配器,允许您使用常见的开源或商业监控系统,包括 Nagios、Ganglia 和 Cacti 等工具。如前所述,MongoDB 手册在其网站上包括一个页面,该页面分享了 MongoDB 可用的监控接口的最新信息(有关该主题的更多信息,请参见 http://docs.mongodb.org/manual/administration/monitoring/ )。

使用 MongoDB 管理服务(MMS)

到目前为止讨论的大多数统计信息也可以通过 MongoDB 管理服务(也称为 MMS)获得。MMS 是 MongoDB,Inc .提供的一种监控服务,它提供了一个可以安装在本地机器上的代理。安装后,您可以通过 MMS 网页添加您的服务器,以便指示代理监控它们。一旦监控开始,您就可以深入到特定的主机,查看 MongoDB 实例的性能统计图表。您可以监控从单个 MongoDB 实例到副本集,直到完整的分片集群,包括配置服务器和 MongoS。MMS 还具有查看这些组的所有单个成员或查看每个组的汇总统计数据的功能。然后,您可以根据您的特定性能需求或 MongoDB 实例中发生的事件来配置发送给您的警报。您可以注册彩信at mms.mongodb.com,我们强烈建议您这样做。没有什么比深入了解每个 MongoDB 节点的性能更强大的了(参见图 9-6 中的例子)。

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

图 9–6。

Viewing statistics via MMS

摘要

保持 MongoDB 安装平稳运行通常只需要很少的努力。在这一章中,您已经看到了如何使用 MongoDB 发行版提供的工具来管理和维护您的系统,以及掌握任何可能出现的问题。

通过学习本章,您现在应该掌握了备份、恢复、升级和监控 MongoDB 实例的技能。您应该熟悉诸如 mongodump 和 mongorestore 之类的工具,知道如何只从某些集合中导入和导出数据,并且熟悉许多可以用来从 MongoDB 实例中导出性能和使用统计数据的管理命令。

最后,必须强调的是(从前面所说的一切中,您可能已经领会到了这一点),从本章中吸取的最重要的教训是:作为数据库系统的管理员,您的首要责任是确保为您的数据提供一个可靠的备份和恢复方案。

十、最优化

Abstract

一位不知名的 Twitter 用户半开玩笑地说:“如果一个 MongoDB 查询运行时间超过 0 毫秒,那么一定有问题。”这是该产品在 2009 年首次亮相时的典型反响。

一位不知名的 Twitter 用户半开玩笑地说:“如果一个 MongoDB 查询运行时间超过 0 毫秒,那么一定有问题。”这是该产品在 2009 年首次亮相时的典型反响。

现实情况是 MongoDB 速度非常快。但是如果您给它错误的数据结构,或者您没有用正确的索引设置集合,MongoDB 可能会像任何数据存储系统一样显著变慢。

MongoDB 还包含一些高级特性,这些特性需要一些调整才能以最佳效率运行。

数据模式的设计也会对性能产生很大的影响;在这一章中,我们将研究一些技术来将您的数据塑造成一种最大限度地利用 MongoDB 的优势并最小化其劣势的形式。

在我们研究如何提高运行在服务器上的查询的性能或者优化数据结构的方法之前,我们先来看看 MongoDB 如何与运行它的硬件进行交互,以及影响性能的因素。然后我们看一下索引,如何使用它们来提高查询的性能,以及如何分析 MongoDB 实例来确定哪些查询性能不好。

优化您的服务器硬件性能

通常,对数据库服务器进行最快速、最便宜的优化是调整运行它的硬件的大小。如果数据库服务器内存太少或使用慢速驱动器,会严重影响数据库性能。虽然这些限制中的一些对于开发环境可能是可接受的,在开发环境中,服务器可能运行在开发者的本地工作站上,但是它们对于生产应用可能是不可接受的,在生产应用中,必须小心计算正确的硬件配置以实现最佳性能。

了解 MongoDB 如何使用内存

MongoDB 使用内存映射文件 I/O 来访问其底层数据存储。这种文件 I/O 方法有一些您应该知道的特征,因为它们会影响运行它的操作系统(OS)的类型和您安装的内存量。

内存映射文件的第一个显著特征是,在现代 64 位操作系统上,可以管理的最大文件大小在 Linux 上约为 128 TB(Linux 虚拟内存地址限制),在 Windows 上约为 8TB(启用日志记录时为 4TB ),因为它对内存映射文件有限制。在 32 位操作系统上,您只能使用 2GB 的数据,因此不建议您使用 32 位操作系统,除非运行小型开发环境。

第二个显著的特征是,内存映射文件使用操作系统的虚拟内存系统,根据需要将数据库文件的所需部分映射到 RAM 中。这可能会给人留下一种稍微令人担忧的印象,即 MongoDB 正在耗尽系统的所有 RAM。事实并非如此,因为 MongoDB 将与其他应用共享虚拟地址空间。并且操作系统将在需要时释放内存给其他进程。使用空闲内存总量作为过度内存消耗的指标并不是一个好的做法,因为一个好的操作系统将确保只有很少或没有“空闲”内存。通过缓存或缓冲磁盘 I/O,所有昂贵的内存都被充分利用。空闲内存是浪费的内存。

通过提供适量的内存,MongoDB 可以将更多需要的数据映射到内存中,从而减少对昂贵的磁盘 I/O 的需求。

一般来说,给 MongoDB 的内存越多,它的运行速度就越快。然而,如果您有一个 2GB 的数据库,那么添加超过 2–3GB 的内存不会有太大的区别,因为整个数据库无论如何都将位于 RAM 中。

了解工作集大小

现在,我们需要讨论与 MongoDB 实例的性能调优相关的更复杂的事情之一,即工作集大小。这个大小表示存储在 MongoDB 实例中“在常规使用过程中”将被访问的数据量光是这句话就应该告诉你,这是一个主观的衡量标准,很难得到一个准确的值。

尽管很难量化,但是理解工作集大小的影响将有助于您更好地优化 MongoDB 实例。主要原则是,对于大多数安装,只有一部分数据需要作为常规操作的一部分进行访问。了解您将定期处理的数据部分使您能够正确调整硬件的大小,从而提高性能。

选择正确的数据库服务器硬件

普遍存在转向低功耗(能源)系统托管服务的压力。然而,许多低功耗服务器使用膝上型电脑或笔记本电脑组件来实现较低的功耗。不幸的是,质量较低的服务器硬件可以使用特别便宜的磁盘驱动器。这种驱动器不适合重型服务器应用,因为它们的磁盘转速较低,从而降低了与驱动器之间的数据传输速率。此外,请确保使用声誉良好的供应商,即您信任的供应商,来组装针对服务器操作进行了优化的系统。还值得一提的是,固态硬盘等更快、更现代的驱动器已经上市,这将大幅提升性能。如果可以安排的话,MongoDB,Inc .团队建议使用 RAID10 来实现性能和冗余。对于那些在云中的人来说,从 Amazon 获得像预配 IOPS 这样的东西是提高磁盘性能和可靠性的一个很好的方法。

如果您计划使用复制或任何需要通过网络连接进行读取的频繁备份系统,您应该考虑添加一个额外的网卡并形成一个单独的网络,以便服务器可以相互通信。这减少了用于将应用连接到服务器的网络接口上传输和接收的数据量,这也会影响应用的性能。

购买硬件时最需要注意的可能就是 RAM。因为 MongoDB 使用内存映射文件,所以有足够的空间将必要的数据保存在可以快速访问的地方是确保高性能的一个好方法。这是您可以链接到前面讨论的工作集概念的地方。在考虑购买硬件时,了解需要分配多少数据是关键。最后,记住你不需要出去买 512GB 的 RAM 安装在一台服务器上;您可以使用分片来分散数据负载(在第 12 章中讨论)。

评估查询性能

MongoDB 有两个主要的优化查询性能的工具:explain()和 MongoDB Profiler(分析器)。profiler 是一个很好的工具,可以用来查找那些性能不佳的查询,并选择候选查询进行进一步的检查,而explain()适合于调查单个查询,因此您可以确定它的性能如何。

熟悉 MySQL 的人可能也熟悉慢速查询日志的用法,它可以帮助您找到消耗大量时间的查询;MongoDB 使用分析器来提供这种能力。

MongoDB 分析器

MongoDB profiler 是一个工具,它记录满足触发条件的每个查询的统计信息和执行计划细节。通过使用--profile--slowms选项启动 MongoD 进程,您可以在每个数据库上单独启用该工具,或者为所有数据库启用该工具(稍后将详细介绍这些值的含义)。这些选项也可以添加到你的mongodb.conf文件中,如果你是这样开始你的 MongoD 进程的话。

一旦启用了探查器,MongoDB 就会将一个文档插入到一个特殊的称为system.profile的有上限的集合中,该文档包含应用提交的每个查询的性能和执行细节信息。您可以使用此集合来检查使用标准集合查询命令记录的每个查询的详细信息。

system.profile集合被限制为最大 1024KB 的数据,这样探查器就不会用日志记录信息填充磁盘。这个限制应该足以捕获几千个甚至是最复杂的查询的概要文件。

Warning

启用探查器后,它会影响服务器的性能,因此让它在生产服务器上运行并不是一个好主意,除非您正在对一些观察到的问题进行分析。不要试图让它永久运行,以便为最近执行的查询提供一个窗口。

启用和禁用数据库探查器

打开 MongoDB 分析器很简单:

$mongo

>use blog

>db.setProfilingLevel(1)

禁用分析器也同样简单:

$mongo

>use blog

>db.setProfilingLevel(0)

MongoDB 还可以只为超过指定执行时间的查询启用探查器。以下示例只记录执行时间超过半秒的查询:

$mongo

>use blog

>db.setProfilingLevel(1,500)

如此处所示,对于分析级别 1,您可以提供以毫秒(ms)为单位的最大查询执行时间值。如果查询运行的时间超过了这个时间量,就会对其进行分析和记录。否则,它将被忽略。这提供了与 MySQL 的慢速查询日志相同的功能。

最后,通过将探查器级别设置为 2,可以为所有查询启用分析。

$mongo

>use blog

>db.setProfilingLevel(2)

查找慢速查询

system.profile集合中的典型记录如下所示:

> db.system.profile.find()

{

"op" : "query"

"ns" : "blog.system.profile"

"query" : {

}

"ntoreturn" : 0

"ntoskip" : 0

"nscanned" : 1

"keyUpdates" : 0

"numYield" : 0

"lockStats" : {

"timeLockedMicros" : {

"r" : NumberLong(60)

"w" : NumberLong(0)

}

"timeAcquiringMicros" : {

"r" : NumberLong(4)

"w" : NumberLong(3)

}

}

"nreturned" : 1

"responseLength" : 370

"millis" : 12

"ts" : ISODate("2013-05-18T05:40:27.106Z")

"client" : "127.0.0.1"

"user" : ""

}

每个记录都包含字段,下面的列表概述了它们是什么以及它们的作用:

  • op:显示操作的类型;它可以是查询、插入、更新、命令或删除。
  • query:正在运行的查询。
  • 运行此查询的完整名称空间。
  • ntoreturn:要返回的单据数量,
  • nscanned:扫描返回本文档的索引条目数。
  • ntoskip:跳过的文档数。
  • keyUpdates:本次查询更新的索引键个数。
  • numYields:该查询将其锁让给另一个查询的次数。
  • lockStats:获取或读写该数据库的锁所花费的微秒数。
  • nreturned:返回的文档数。
  • responseLength:响应的字节长度。
  • millis:执行查询所用的毫秒数。
  • ts:以 UTC 格式显示时间戳,指示查询的执行时间。
  • client:运行此查询的客户端的连接详细信息。
  • user:运行此操作的用户。

因为system.profile集合只是一个普通的集合,所以您可以使用 MongoDB 的查询工具快速找到有问题的查询。

下一个示例查找执行时间超过 10ms 的所有查询。在这种情况下,您可以只在system.profile集合中查询millis >10的情况,然后按照执行时间降序排列结果:

> db.system.profile.find({millis:{$gt:10}}).sort({millis:-1})

{ "op" : "query", "ns" : "blog.system.profile", "query" : { }, "ntoreturn" : 0, "ntoskip" : 0, "nscanned" : 1, "keyUpdates" : 0, "numYield" : 0, "lockStats" : { "timeLockedMicros" : { "r" : NumberLong(60), "w" : NumberLong(0) }, "timeAcquiringMicros" : { "r" : NumberLong(4), "w" : NumberLong(3) } }, "nreturned" : 1, "responseLength" : 370, "millis" : 12, "ts" : ISODate("2013-05-18T05:40:27.106Z"), "client" : "127.0.0.1", "allUsers" : [ ], "user" : "" }

如果您还知道您的问题发生在特定的时间范围内,那么您可以使用ts字段添加查询词,将范围限制在所需的时间片。

增加配置文件集合的大小

如果您发现由于某种原因您的配置文件集合太小,您可以增加它的大小。

首先,您需要在希望增加其概要文件集合大小的数据库上禁用概要文件分析,以确保在执行此操作时没有任何内容写入其中:

$mongo

>use blog

>db.setProfilingLevel(0)

接下来,您需要删除现有的system.profile集合:

>db.system.profile.drop()

删除集合后,您现在可以使用createCollection命令创建新的 profiler 集合,并指定所需的字节大小。下面的示例创建一个上限为 50MB 的集合。它使用的表示法是将 50 字节转换为千字节,然后再转换为兆字节,每增加一次,就要乘以 1024:

>db.createCollection( "system.profile", { capped: true, size: 50 * 1024 * 1024 } )

{ "ok" : 1 }

有了新的更大的封顶集合,现在可以重新启用概要分析了:

>db.setProfilingLevel(2)

使用 explain()分析特定查询

如果您怀疑某个查询的性能不如预期,您可以使用explain()修饰符来查看 MongoDB 是如何执行查询的。

当您将explain()修饰符添加到查询中时,MongoDB 在执行时会返回一个描述查询如何处理的文档,而不是指向结果的光标。以下查询针对博客文章的数据库运行,表明该查询必须扫描 13,325 条记录,才能形成返回所有文章的游标:

$mongo

>use blog

> db.posts.find().explain()

{

"cursor" : "BasicCursor"

"isMultiKey" : false

"n" : 13235

"nscannedObjects" : 13235

"nscanned" : 13235

"nscannedObjectsAllPlans" : 13235

"nscannedAllPlans" : 13235

"scanAndOrder" : false

"indexOnly" : false

"nYields" : 0

"nChunkSkips" : 0

"millis" : 0

"indexBounds" : {

}

"server" : "Pixl.local:27017"

}

您可以看到表10–1中列出的explain()返回的字段。

表 10–1。

Elements Returned by explain()

| 元素 | 描述 | | --- | --- | | `Cursor` | 指示为枚举结果而创建的游标的类型。通常,这是下列之一:`BasicCursor`,自然顺序阅读光标;一个`BtreeCursor`,它是一个索引光标;或者是一个`GeoSearchCursor`,这是一个使用地理空间索引的查询。 | | `indexBounds` | 指示索引查找中使用的最小/最大值。 | | `nScanned` | 指示为查找查询中的所有对象而扫描的索引条目数。 | | `nScannedObjects` | 指示被扫描的实际对象的数量,而不仅仅是它们的索引项。 | | `n` | 指示光标上的项数(即要返回的项数)。 | | `millis` | 指示执行查询所用的毫秒数。 | | `nscannedAllPlans` | 表示所有尝试计划的`nscanned`值。 | | `nscannedObjectsAllPlans` | 表示所有尝试计划的`nScannedObjects`值。 | | `scanAndOrder` | 一个 true/false 值,指示文档是否需要阅读才能排序,而不是利用索引。 | | `indexOnly` | 表示不需要扫描任何文档来返回查询结果;所有被查询和返回的字段都在一个索引上。 | | `nYields` | 该查询产生其读锁以允许执行写操作的次数。 | | `nChunkSkips` | 由于活动块迁移而跳过的文档数。 | | `server` | 执行该查询的服务器。 |

使用 Profiler 和 explain()优化查询

现在让我们浏览一个真实的优化场景,看看我们如何使用 MongoDB 的 profiler 和explain()工具来修复一个真实应用的问题。

本章中讨论的例子是基于一个小的示例博客应用。该数据库具有获取与特定标签相关联的帖子的功能;在这种情况下,它是even标签。假设您已经注意到这个函数运行缓慢,因此您想确定是否有问题。

让我们从编写一个小程序开始,用数据填充前面提到的数据库,这样我们就可以运行查询,演示优化过程。

<?php

// Get a connection to the database

$mongo = new MongoClient();

$db=$mongo->blog;

// First let's get the first AuthorsID

// We are going to use this to fake a author

$author = $db->authors->findOne();

if(!$author){

die("There are no authors in the database");

}

for( $i = 1; $i < 10000; $i++){

$blogpost=array();

$blogpost['author'] = $author['_id'];

$blogpost['Title'] = "Completely fake blogpost number {$i}";

$blogpost['Message'] = "Some fake text to create a database of blog posts";

$blogpost['Tags'] = array();

if($i%2){

// Odd numbered blogs

$blogpost['Tags'] = array("blog", "post", "odd", "tag{$i}");

} else {

// Even numbered blogs

$blogpost['Tags'] = array("blog", "post", "even", "tag{$i}");

}

$db->posts->insert($blogpost);

}

?>

这个程序在blog数据库的authors集合中找到第一个作者,然后假装这个作者非常多产。它以作者的名义创建了 10,000 个虚假的博客帖子,所有这些都是在眨眼之间完成的。帖子读起来没什么意思;然而,它们被交替分配了oddeven标签。这些标签将用来演示如何优化一个简单的查询。

下一步是将程序保存为fastblogger.php,然后使用命令行 PHP 工具运行它:

$php fastblogger.php

接下来,您需要启用数据库概要分析器,您将使用它来确定是否可以改进示例的查询:

$ mongo

> use blog

switched to db blog

> show collections

authors

posts

...

system.profile

tagcloud

...

users

> db.setProfilingLevel(2)

{ "was" : 0, "slowms" : 100, "ok" : 1 }

现在稍等片刻,让命令生效,打开所需的集合,然后执行其他任务。接下来,您想要模拟让博客网站访问所有带有even标签的博客文章。通过执行站点可以用来实现此功能的查询来实现这一点:

$Mongo

use blog

$db.posts.find({Tags:"even"})

...

如果您在profiler集合中查询超过 5 毫秒的结果,您应该会看到类似这样的内容:

>db.system.profile.find({millis:{$gt:5}}).sort({millis:-1})

{ "op" : "query", "ns" : "blog.posts", "query" : { "tags" : "even" }, "ntoreturn" : 0, "ntoskip" : 0, "nscanned" : 19998, "keyUpdates" : 0, "numYield" : 0, "lockStats" : { "timeLockedMicros" : { "r" : NumberLong(12869), "w" : NumberLong(0) }, "timeAcquiringMicros" : { "r" : NumberLong(5), "w" : NumberLong(3) } }, "nreturned" : 0, "responseLength" : 20, "millis" : 12, "ts" : ISODate("2013-05-18T09:04:32.974Z"), "client" : "127.0.0.1", "allUsers" : [ ], "user" : "" }...

这里返回的结果显示,一些查询花费的时间超过 0 毫秒(记住本章开头的引文)。

接下来,您希望为第一个(也是性能最差的)查询重建查询,这样您就可以看到返回了什么。前面的输出表明性能差的查询正在查询blog.posts,查询词是{Tags:"even"}。最后,您可以看到这个查询花费了 15 毫秒来执行。

重建的查询如下所示:

>db.posts.find({Tags:"even"})

{ "_id" : ObjectId("4c727cbd91a01b2a14010000"), "author" : ObjectId("4c637ec8b8642fea02000000"), "Title" : "Completly fake blogpost number 2", "Message" : "Some fake text to create a database of blog posts", "Tags" : [ "blog", "post", "even", "tag2" ] }

{ "_id" : ObjectId("4c727cbd91a01b2a14030000"), "author" : ObjectId("4c637ec8b8642fea02000000"), "Title" : "Completly fake blogpost number 4", "Message" : "Some fake text to create a database of blog posts", "Tags" : [ "blog", "post", "even", "tag4" ] }

{ "_id" : ObjectId("4c727cbd91a01b2a14050000"), "author" : ObjectId("4c637ec8b8642fea02000000"), "Title" : "Completly fake blogpost number 6", "Message" : "Some fake text to create a database of blog posts", "Tags" : [ "blog", "post", "even", "tag6" ] }

...

这一结果应该不足为奇;创建这个查询的明确目的是演示如何查找和修复一个缓慢的查询。

目标是找出如何让查询运行得更快,所以使用explain()函数来确定 MongoDB 如何执行这个查询:

> db.posts.find({Tags:"even"}).explain()

{

"cursor" : "BasicCursor"

"isMultiKey" : false

"n" : 14997

"nscannedObjects" : 29997

"nscanned" : 29997

"nscannedObjectsAllPlans" : 29997

"nscannedAllPlans" : 29997

"scanAndOrder" : false

"indexOnly" : false

"nYields" : 0

"nChunkSkips" : 0

"millis" : 27

"indexBounds" : {

}

"server" : "Pixl.local:27017"

}

从这里的输出可以看出,查询没有使用任何索引。explain()函数显示该查询使用了一个“BasicCursor”,这意味着该查询只是对集合的记录进行简单的扫描。具体来说,它正在逐个扫描数据库中的所有记录,以找到标签(全部 9999 个标签);这个过程需要 27 毫秒。这听起来时间可能不长,但是如果您在网站的热门页面上使用这个查询,将会给磁盘 I/O 带来额外的负载,并给 web 服务器带来巨大的压力。因此,在创建页面时,该查询会导致与 web 浏览器的连接保持更长时间的打开。

Note

如果您看到一个详细的查询说明,显示扫描的记录(nscanned)比它返回的记录(n)多得多,那么该查询可能是索引的候选。

下一步是确定在Tags字段上添加索引是否会提高查询的性能:

> db.posts.ensureIndex({Tags:1})

现在再次运行explain()函数,查看添加索引的效果:

> db.posts.find({Tags:"even"}).explain()

{

"cursor" : "BtreeCursor Tags_1"

"isMultiKey" : true

"n" : 14997

"nscannedObjects" : 14997

"nscanned" : 14997

"nscannedObjectsAllPlans" : 14997

"nscannedAllPlans" : 14997

"scanAndOrder" : false

"indexOnly" : false

"nYields" : 0

"nChunkSkips" : 0

"millis" : 4

"indexBounds" : {

"Tags" : [

[

"even"

"even"

]

]

}

"server" : "Pixl.local:27017"

}

查询的性能有了显著的提高。您可以看到该查询现在使用了由Tags_1索引驱动的BtreeCursor。扫描的记录数量从 29,997 条减少到了您期望查询返回的 14,997 条,执行时间也减少到了 4 毫秒。

Note

最常见的索引类型,也是 MongoDB 唯一使用的索引类型,是 btree(二叉树)。BtreeCursor是一个 MongoDB 数据游标,它使用二叉树索引在文档间导航。Btree 索引在数据库系统中非常常见,因为它们提供了快速的插入和删除,而且在用于遍历或排序数据时还提供了合理的性能。

管理索引

您现在已经看到了引入精心选择的索引会产生多大的影响。

正如您在第 3 章中了解到的,MongoDB 的索引既用于查询(findfindOne)也用于排序。如果您打算在集合中使用很多排序,那么您应该添加符合您的排序规范的索引。如果在排序规范中没有字段索引的集合上使用sort(),那么如果超出了内部排序缓冲区的最大大小,可能会得到一条错误消息。因此,为排序创建索引是一个好主意。在接下来的几节中,我们将再次触及基础知识,但也会添加一些与如何管理和操作系统中的索引相关的细节。我们还将介绍这些索引与一些样本的关系。

当您向集合添加索引时,MongoDB 必须维护它,并在您每次执行任何写操作时更新它(例如,updatesinsertsdeletes)。如果集合中有太多索引,可能会对写入性能产生负面影响。

索引最适合用于大多数访问是读访问的集合。对于日志记录系统中使用的大量写操作的集合,引入索引会降低每秒钟流入集合的峰值文档数。

Warning

此时,每个集合最多可以有 64 个索引。

列出索引

MongoDB 有一个简单的助手函数getIndexes(),用于列出给定集合的索引。执行时,它将打印一个 JSON 数组,该数组包含给定集合上每个索引的详细信息,包括它们引用的字段或元素以及您可能在该索引上设置的任何选项。

$mongo

>use blog

>db.posts.getIndexes()

[

{

"v" : 1

"key" : {

"_id" : 1

}

"ns" : "blog.posts"

"name" : "_id_"

}

]

MongoDB 在每个数据库中维护一个名为system.indexes的特殊集合。该集合跟踪数据库中所有集合上创建的所有索引。

system.indexes收藏就像任何普通的收藏一样。您可以列出其内容,对其运行查询,或者执行您可以使用典型集合完成的常规任务。

以下示例列出了简单数据库中的索引:

$mongo

>use blog

>db.system.indexes.find()

{ "v" : 1, "key" : { "_id" : 1 }, "ns" : "blog.posts", "name" : "_id_" }

{ "v" : 1, "key" : { "_id" : 1 }, "ns" : "blog.authors", "name" : "_id_" }

blog数据库没有任何用户定义的索引,但是您可以看到为您的两个集合上的_id字段自动创建的两个索引:postsauthors。您不必做任何事情来创建或删除这些身份索引;每当创建或删除一个集合时,MongoDB 都会创建和删除它们。

当您在元素上定义索引时,MongoDB 将构造一个内部 btree 索引,它将使用该索引来高效地定位文档。如果找不到合适的索引,MongoDB 将扫描集合中的所有文档,以找到满足查询的记录。

创建简单索引

MongoDB 提供了用于向集合添加新索引的ensureIndex()函数。这个函数首先检查是否已经用相同的规范创建了一个索引。如果有,那么ensureIndex()就返回那个索引。这意味着您可以任意多次调用ensureIndex(),但这不会导致为您的集合创建大量额外的索引。

以下示例定义了一个简单的索引:

$mongo

>use blog

>db.posts.ensureIndex({Tags:1})

这个例子在Tags字段上创建了一个简单的升序 btree 索引。相反,创建一个降序索引只需要一个小小的改动:

>db.posts.ensureIndex({Tags:-1})

要索引嵌入文档中的字段,可以使用普通的点标记寻址方案;也就是说,如果您有一个位于comments子文档中的count字段,您可以使用以下语法对其进行索引:

>db.posts.ensureIndex({"comments.count":1})

如果您指定一个数组类型的文档字段,那么索引将包含数组的所有元素作为单独的索引项。这称为多键索引,每个文档都链接到索引中的多个值。如果您回过头来检查我们之前查询的explain()输出,您可以看到这里提到了这一点。

MongoDB 有一个特殊的操作符all,用于执行希望只选择包含您提供的所有术语的文档的查询。在blog数据库示例中,您有一个包含元素Tagsposts集合。这个元素包含了与文章相关的所有标签。以下查询查找同时具有sailormoon标签的所有文章:

>db.posts.find({Tags:{$all: ['sailor', 'moon']}})

如果在Tags字段上没有多键索引,查询引擎将不得不扫描集合中的每个文档,以查看其中一个术语是否存在,如果存在,则检查两个术语是否都存在。

创建复合索引

简单地为任何查询中提到的每个字段创建一个单独的索引可能很有诱惑力。虽然这可以在不需要太多考虑的情况下加快查询速度,但不幸的是,这会对添加和删除数据库中的数据产生重大影响,因为这些索引每次都需要更新。同样重要的是要注意,在 MongoDB 2.4 和更早的版本中,只有一个索引会被用来满足查询的结果,所以添加许多小索引通常不会帮助查询的执行。

复合索引提供了一种减少集合中索引数量的好方法,允许您将多个字段组合成一个索引,因此您应该尽可能使用复合索引。

复合索引主要有两种类型:子文档索引和手动定义的复合索引。

MongoDB 有一些规则允许它对不使用所有组件键的查询使用复合索引。理解这些规则使您能够构建一组复合索引,覆盖您希望对集合执行的所有查询,而不必单独索引每个元素(从而避免前面提到的对插入/更新性能的附带影响)。

复合索引可能没有用的一个方面是在排序中使用索引。排序不擅长使用复合索引,除非术语列表和排序方向与索引结构完全匹配。

当您使用子文档作为索引键时,用于建立多键索引的元素的顺序与它们在子文档的内部 BSON 表示中出现的顺序相匹配。在许多情况下,这并不能让您对创建索引的过程有足够的控制。

要绕过这一限制,同时保证查询使用以所需方式构造的索引,需要确保使用相同的子文档结构来创建形成查询时使用的索引,如下例所示:

>db.articles.find({author:{name: 'joe', email: 'joe@blogger.com '}))

还可以通过命名索引中要组合的所有字段,然后指定组合它们的顺序,来显式创建复合索引。以下示例说明了如何手动构建复合索引:

>db.posts.ensureIndex({"author.name":1, "author.email":1})

指定索引选项

创建索引时可以指定几个有趣的选项,比如创建唯一索引或启用后台索引;在接下来的章节中,您将了解更多关于这些选项的信息。您将这些选项指定为ensureIndex()函数的附加参数,如下例所示:

>db.posts.ensureIndex({author:1}, {option1:true, option2:true, ..... })

使用{background:true}在后台创建索引

当您第一次使用ensureIndex()函数指示 MongoDB 创建索引时,服务器必须读取集合中的所有数据并创建指定的索引。默认情况下,索引构建在前台完成,在索引操作完成之前,对集合数据库的所有操作都被阻止。

MongoDB 还包含一个特性,允许在后台执行索引的初始构建。在建立索引时,其他连接对该数据库的操作不会被阻止。在索引建立之前,任何查询都不会使用它,但是服务器将允许读写操作继续进行。一旦索引操作完成,所有需要该索引的查询将立即开始使用它。值得注意的是,虽然可以在后台构建索引,但它需要更长的时间才能完成。

因此,您可能希望研究构建索引的其他策略。最好的策略可能是在一个副本集中轮流执行构建。为此,您一次停止一个辅助节点,并在没有其--replSet参数的情况下在不同的端口上启动它,暂时使它成为一个独立的节点。然后,您可以放心地在这个辅助服务器上执行索引构建。索引构建完成后,像往常一样启动辅助服务器,让它重新加入副本集并跟上。对所有次要成员重复此过程。最后,停止主服务器,对--replSet执行相同的删除,并为它构建一个索引进程,然后像往常一样让它重新加入副本集。通过以这种方式旋转您的副本集,您可以在没有停机或中断的情况下建立索引!

Note

该索引将在后台构建。但是,如果从 MongoDB shell 发出命令,发起请求的连接将被阻塞。在索引操作完成之前,在此连接上发出的命令不会返回。乍一看,这似乎与它是一个背景索引的想法相矛盾。但是,如果您同时打开了另一个 MongoDB shell,您会发现在索引构建过程中,对该集合的查询和更新会不受阻碍地运行。只有发起连接被阻塞。这不同于您看到的简单的ensureIndex()命令的行为,该命令不在后台运行,因此第二个 MongoDB shell 上的操作也会被阻塞。

KILLING THE INDEXING PROCESS

如果您认为当前的索引进程已经挂起或者花费了太长时间,您也可以终止它。您可以通过调用killOp()函数来实现:

> db.killOp(<operation id>)

要运行 killOp,您需要知道操作的操作 ID。通过运行db.currentOp()命令,可以获得 MongoDB 实例上当前运行的所有操作的列表。

注意,当您调用killOp()命令时,部分索引也将再次被删除。这可以防止数据库中出现不完整或不相关的数据。

创建具有唯一键{unique:true}的索引

当您指定unique选项时,MongoDB 会创建一个索引,其中所有的键都必须不同。这意味着,如果您试图插入一个索引键与现有文档的键相匹配的文档,MongoDB 将返回一个错误。这对于您希望确保没有两个人拥有相同身份(即相同的userid)的领域非常有用。

但是,如果要向已经填充了数据的现有集合添加唯一索引,则必须确保已经对键进行了重复数据删除。在这种情况下,如果任意两个键不唯一,创建索引的尝试将会失败。

unique选项适用于简单和复合索引,但不适用于多键值索引,在多键值索引中它们没有多大意义。

如果插入的文档缺少一个被指定为惟一键的字段,那么 MongoDB 将自动插入该字段,但是会将其值设置为null。这意味着您只能将一个缺少关键字段的文档插入到这样的集合中;任何额外的null值都意味着这个键不是惟一的,正如所要求的那样。

使用{dropdups:true}自动删除重复项

如果您想为已知存在重复键的字段创建唯一索引,您可以指定dropdups选项。该选项指示 MongoDB 删除会导致索引创建失败的文档。在这种情况下,MongoDB 将保留它在集合的自然排序中找到的第一个文档,但随后丢弃任何其他会导致违反索引约束的文档。

Warning

使用dropdups选项时需要非常小心,因为它会导致文档从您的收藏中删除。

在使用此选项之前,您应该非常清楚您的集合中的数据;否则,您可能会得到意想不到的(更不用说不想要的)行为。对希望使键唯一的集合运行组查询是一个非常好的主意;这将使您能够在执行此选项之前确定被视为重复的文档数量。

使用{sparse:true}创建稀疏索引

有时,只为包含给定字段条目的文档创建索引是值得的。例如,假设您想要索引电子邮件,并且您知道并非所有电子邮件都有“抄送”或“密件抄送”字段。如果您在 CC 或 BCC 上创建索引,那么所有文档都将添加一个“null”值,除非您指定一个稀疏索引。这是一种节省空间的机制,因为您只对有效文档而不是所有文档进行索引。当然,这对于任何运行和使用稀疏索引的查询都有影响;因为可能存在查询中未评估的文档。

TTL 索引

在计算术语中,TTL(生存时间)是一种通过指定一个点来赋予特定数据或请求一个生命周期的方式,在该点上它变得无效。这对于存储在 MongoDB 实例中的数据也很有用,因为自动删除旧数据通常很好。为了创建一个 TTL 索引,你必须添加一个expireAfterSeconds标志和一个秒值到一个单一的(非复合的)索引。这将表明,当 TTL 删除任务下次执行时,任何索引字段大于给定 TTL 值的文档都将被删除。由于删除任务每 60 秒运行一次,因此在删除旧文档之前可能会有一些延迟。

Warning

被索引的字段必须是 BSON 日期类型;否则不会被评估为删除。如下例所示,当从 shell 中查询时,BSON 日期将显示为ISODate

例如,假设我们想从博客的comments集合中自动删除任何内容,该集合的创建时间戳超过了某个年龄。以这个来自comments集合的示例文档为例:

>db.comments.find();

{

"_id" : ObjectId("519859b32fee8059a95eeace")

"author" : "david"

"body" : "foo"

"ts" : ISODate("2013-05-19T04:48:51.540Z")

"tags" : [ ]

}

假设我们想要删除任何超过 28 天的评论。我们算出 28 天有 2419200 秒长。然后,我们按如下方式创建索引:

>db.comments.ensureIndex({ts:1},{ expireAfterSeconds: 2419200})

当此文档比当前系统时间早 2,419,200 秒时,它将被删除。您可以通过使用以下语法创建一个超过 2,419,200 秒的文档来进行测试:

date = new Date(new Date().getTime()-2419200000);

db.comments.insert({ "author" : test", "body" : "foo", "ts" : date, "tags" : [] });

现在只需等待一分钟,该文件应被删除。

Note

当您设置 TTL 索引时,MongoDB 会在给定的集合上设置usePowerOf2Sizes标志。这将使每个文件的存储空间标准化。这允许空间在被删除后被未来的文档更有效地重用。

文本搜索索引

MongoDB 2.4 引入了一种新的索引类型——文本索引,它允许您执行全文搜索!第 8 章详细讨论了文本索引特性,但在我们研究优化时,这里有必要快速总结一下。文本搜索一直是 MongoDB 的一个理想特性,因为它允许您在一个大的文本块中搜索特定的单词或文本。文本搜索相关性的最好例子是对博客文章正文的搜索功能。这种搜索允许您在文档的一个文本字段(如正文)或多个文本字段(如正文和注释)中查找单词或短语。因此,为了创建文本索引,我们运行以下命令:

>db.posts.ensureIndex( { body: "text" } )

Note

文本搜索不区分大小写,这意味着它将忽略大小写;“mongodb”和“MongoDB”被视为同一文本。

现在我们可以使用文本索引和text命令进行搜索。为此,我们使用runCommand语法来使用text命令,并为其提供一个要搜索的值:

>db.posts.runCommand( "text", { search: "MongoDB" } )

您的文本搜索结果将按相关性顺序返回给您。

Warning

MongoDB 文本搜索仍然相对较新,并且正在经历快速发展。

删除索引

您可以选择删除集合中的所有索引或仅删除一个特定索引。使用以下函数删除集合中的所有索引:

>db.posts.dropIndexes()

要从集合中删除单个索引,可以使用与使用ensureIndex()创建索引的语法相对应的语法:

>db.posts.dropIndex({"author.name":1, "author.email":1});

重新索引收藏

如果您怀疑集合中的索引已损坏,例如,如果您的查询得到不一致的结果,那么您可以强制对受影响的集合重新建立索引。

这将强制 MongoDB 删除并重新创建指定集合上的所有索引(有关如何检测和解决索引问题的更多信息,请参见第 9 章),如下例所示:

> db.posts.reIndex()

{

"nIndexesWas" : 2

"msg" : "indexes dropped for collection"

"nIndexes" : 2

"indexes" : [

{

"key" : {

"_id" : 1

}

"ns" : "blog.posts"

"name" : "_id_"

}

{

"key" : {

"Tags" : 1

}

"ns" : "blog.posts"

"name" : "Tags_1"

}

]

"ok" : 1

}

输出列出了该命令重新构建的所有索引,包括键。此外,nIndexWas:字段显示了在运行命令之前存在多少个索引,而nIndex:字段给出了命令完成之后的索引总数。如果两个值不相同,则意味着在重新创建集合中的某些索引时出现了问题。

MongoDB 如何选择它将使用的索引

当一个数据库系统需要运行一个查询时,它必须组装一个查询计划,查询计划是它执行查询所必须运行的步骤的列表。每个查询可能有多个查询计划,这些计划同样可以产生相同的结果。然而,每个计划都可能包含比其他计划执行起来更昂贵的元素。例如,扫描集合中的所有记录是一项开销很大的操作,任何包含这种方法的计划都会很慢。这些计划还可以包括用于查询和排序操作的备选索引列表。

道路指示很好地说明了这一概念。如果你想从一个拐角到达街区的斜对面,那么“左转,然后右转”和“右转,然后左转”是到达对面拐角的同样有效的计划。然而,如果其中一条路线有两个停车标志,而另一条没有,那么前一种方法是一个更昂贵的方案,而后一种方法是最好的方案。在执行查询时,集合扫描可能会成为潜在的停止标志。

MongoDB 附带了一个名为查询分析器的组件。这个组件接受一个查询和查询目标的集合,然后为 MongoDB 生成一组要执行的计划。本章前面描述的explain()函数列出了所使用的计划和为给定查询生成的一组备选计划。

MongoDB 还附带了一个查询优化器组件。该组件的工作是选择最适合运行特定查询的执行计划。在大多数关系数据库系统中,查询优化器使用关于表中键的分布、记录数量、可用索引、先前选择的有效性以及各种加权因子的统计信息来计算每种方法的成本。然后,它选择最便宜的计划来使用。

MongoDB 中的查询优化器比典型的 RDBMS 查询分析器既笨又聪明。例如,它不使用基于成本的方法来选择执行计划;相反,它并行运行所有这些程序,并使用返回结果最快的程序,在获胜者越过终点线后终止所有其他程序。因此,MongoDB 中的查询分析器使用一种简单的机制(dumb)来确保它总是获得最快的结果(smart ),并且这些计划的结果被缓存并在接下来的 1000 次写操作中重用,直到执行解释或在该集合上添加、删除或重新索引任何索引。

使用 hint()强制使用特定索引

MongoDB 中的查询优化器从查询的候选索引集中选择最佳索引。它使用刚才概述的方法来尝试将最佳索引或索引集与给定的查询进行匹配。但是,在有些情况下,查询优化器可能没有做出正确的选择,在这种情况下,可能有必要帮助组件。

在这种情况下,您可以向查询优化器提供提示,促使组件做出不同的选择。例如,如果您已经使用了explain()来显示您的查询正在使用哪些索引,并且您认为您希望它对给定的查询使用不同的索引,那么您可以强制查询优化器这样做。

让我们看一个例子。假设您有一个名为author的子文档的索引,其中有nameemail字段。还假设您有以下定义的索引:

>db.posts.ensureIndex({author.name:1, author.email:1})

您可以使用以下提示来强制查询优化器使用定义的索引:

>db.posts.find({author:{name:'joe', email: 'joe@mongodb.com '}}).hint({author.name:1, author.email:1})

如果出于某种原因,您希望强制查询不使用索引,也就是说,如果您希望使用集合文档扫描作为选择记录的方法,您可以使用以下提示来完成此操作:

>db.posts.find({author:{name: 'joe', email: 'joe@mongodb.com '}}).hint({$natural:1})

优化小对象的存储

索引是加速数据查询的关键。但是影响应用性能的另一个因素是它所访问的数据的大小。与具有固定模式的数据库系统不同,MongoDB 将每个记录的所有模式数据存储在记录本身中。因此,对于每个字段具有大量数据内容的大型记录,模式数据与记录数据的比率较低;但是,对于具有小数据值的小记录,这个比率可能会变得惊人地大。

考虑一个 MongoDB 非常适合的应用类型中的常见问题:日志记录。MongoDB 非凡的写入速度使得将事件作为小文档流式传输到集合中非常高效。但是,如果您想进一步优化执行该功能的速度,您可以做几件事情。

首先,您可以考虑批量插入。MongoDB 附带了一个多文档insert()调用。您可以使用此调用同时将几个事件放入集合中。这导致通过数据库接口 API 的往返次数减少。

其次(也是更重要的),您可以减少字段名的大小。如果字段名较小,MongoDB 可以在将事件记录刷新到磁盘之前将更多的事件记录打包到内存中。这使得整个系统效率更高。

例如,假设您有一个用于记录三个字段的集合:一个时间戳、一个计数器和一个用于指示数据源的四字符字符串。您的数据的总存储大小如表10–2所示。

表 10–2。

The Logging Example Collection Storage Size

| 田 | 大小 | | --- | --- | | 时间戳 | 8 字节 | | 整数 | 4 字节 | | 线 | 4 字节 | | 总数 | 16 字节 |

如果您使用tsnsrc作为字段名,那么字段名的总大小是 6 个字节。与数据大小相比,这是一个相对较小的值。但是现在假设您决定将字段命名为WhenTheEventHappenedNumberOfEventsSourceOfEvents。在这种情况下,字段名的总大小是 48 个字节,是数据本身大小的三倍。如果您将 1TB 的数据写入一个集合,那么您将存储 750GB 的字段名称,但只有 250GB 的实际数据。

这不仅浪费了磁盘空间。它还会影响系统性能的所有其他方面,包括索引大小、数据传输时间,以及(可能更重要的是)使用宝贵的系统 RAM 来缓存数据文件。

在日志应用中,您还需要避免在写入记录时在集合上添加索引;如前所述,维护索引需要时间和资源。相反,您应该在开始分析数据之前立即添加索引。

最后,您应该考虑使用将事件流分成多个集合的模式。例如,您可以将每天的事件写入单独的集合。较小的集合需要较少的时间进行索引和分析。

摘要

在这一章中,我们研究了一些跟踪 MongoDB 查询中缓慢性能的工具,以及加速由此产生的缓慢查询的潜在解决方案。我们还研究了一些优化数据存储的方法。例如,我们研究了确保充分利用 MongoDB 服务器可用资源的方法。

本章中描述的特定技术使您能够优化数据并调优存储数据的 MongoDB 系统。最佳方法因应用而异,取决于许多因素,包括应用类型、数据访问模式、读/写比率等。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值