mongod每天要产生大量的日志,如果不去管它,我这边一周大约有10~20G的日志出来,可见日志轮询是必须的。

mongodb本身支持日志轮询的信号,所以我的日志轮询脚本如下:

#!/bin/sh

log_dir="/var/log/mongo/"
killall -SIGUSR1 mongod
/usr/bin/find ${log_dir} -name ‘mongod.log.*’ -mtime +5  -exec rm -f {} ;

然后每天零点零分跑一下。

我有6台mongodb,上面的脚本跑了一两个月一直挺正常。但是最近其中一台在做日志轮询的时候可能是产生了死锁,因为我首先收到了读写锁过多的报警,日志轮询没有成功,客户端也连不进去。收到报警后就连到服务器去看,查查日志,确定问题后重启mongodb就正常了。

这个脚本造成服务中断14分钟,mongodb用的是最新的稳定版本1.8.1,mongodb的稳定性还有待提高。

 

发生这个问题之后,上面的脚本就不敢再用了,对脚本进行了更改:

#!/bin/sh

log_dir="/var/log/mongo/"
date=`date +%Y-%m-%d`

cat ${log_dir}/mongod.log >> ${log_dir}/mongod.log.${date}
cat /dev/null > ${log_dir}/mongod.log

/usr/bin/find ${log_dir} -name ‘mongod.log.*’ -mtime +5  -exec rm -f {} ;

然后让他一小时跑一次,目前看还行,就是要多占一点磁盘IO,但是稳定第一,可以确保不会出现上面的问题了。

 

mongodb的Replication是通过一个日志来存储写操作的,这个日志就叫做Oplog。

在默认情况下,对于64位的mongodb,oplogs都相当大-可能是5%的磁盘空间。通常而言,这是一种合理的设置。可以通过mongod –oplogSize来改变Oplog的日志大小。

 

Oplog的collectio为:

local.oplog.$main for master/slave replication;

local.oplog.rs for replica sets

如 master/slave replication:

> use local               
switched to db local
> db.oplog.$main.help()
DBCollection help
        db.oplog.$main.find().help() – show DBCursor help
        db.oplog.$main.count()
        db.oplog.$main.dataSize()
        db.oplog.$main.distinct( key ) – eg. db.oplog.$main.distinct( ‘x’ )
        db.oplog.$main.drop() drop the collection
        db.oplog.$main.dropIndex(name)
        db.oplog.$main.dropIndexes()
        db.oplog.$main.ensureIndex(keypattern,options) – options should be an object with these possible fields: name, unique, dropDups
        db.oplog.$main.reIndex()
        db.oplog.$main.find( [query] , [fields]) – first parameter is an optional query filter. second parameter is optional set of fields to return.
                                           e.g. db.oplog.$main.find( { x : 77 } , { name : 1 , x : 1 } )
        db.oplog.$main.find(…).count()
        db.oplog.$main.find(…).limit(n)
        db.oplog.$main.find(…).skip(n)
        db.oplog.$main.find(…).sort(…)
        db.oplog.$main.findOne([query])
        db.oplog.$main.findAndModify( { update : … , remove : bool [, query: {}, sort: {}, ‘new’: false] } )
        db.oplog.$main.getDB() get DB object associated with collection
        db.oplog.$main.getIndexes()
        db.oplog.$main.group( { key : …, initial: …, reduce : …[, cond: …] } )
        db.oplog.$main.mapReduce( mapFunction , reduceFunction , <optional params> )
        db.oplog.$main.remove(query)
        db.oplog.$main.renameCollection( newName , <dropTarget> ) renames the collection.
        db.oplog.$main.runCommand( name , <options> ) runs a db command with the given name where the first param is the collection name
        db.oplog.$main.save(obj)
        db.oplog.$main.stats()
        db.oplog.$main.storageSize() – includes free space allocated to this collection
        db.oplog.$main.totalIndexSize() – size in bytes of all the indexes
        db.oplog.$main.totalSize() – storage allocated for all data and indexes
        db.oplog.$main.update(query, object[, upsert_bool, multi_bool])
        db.oplog.$main.validate() – SLOW
        db.oplog.$main.getShardVersion() – only for use with sharding
> db.oplog.$main.find()
{ "ts" : { "t" : 1294582140000, "i" : 14 }, "op" : "d", "ns" : "mixi_top_city.building_90", "b" : true, "o" : { "_id" : "6380690_441_30_29" } }
{ "ts" : { "t" : 1294582140000, "i" : 15 }, "op" : "i", "ns" : "mixi_top_city.building_90", "o" : { "_id" : "6380690_441_24_24", "uid" : "6380690", "x" : 24, "y" : 24, "pos" : 1, "btime" : 1294154452, "ntime" : 1294154452, "bid" : 71, "extprop" : 0, "status" : 0, "ucid" : 441 } }
{ "ts" : { "t" : 1294582140000, "i" : 16 }, "op" : "u", "ns" : "mixi_top_city.building_64", "o2" : { "_id" : "16702364_459_14_14" }, "o" : { "$set" : { "status" : 1 } } }
{ "ts" : { "t" : 1294582140000, "i" : 17 }, "op" : "u", "ns" : "mixi_top_city.building_08", "o2" : { "_id" : "6223408_391_30_28" }, "o" : { "$set" : { "ntime" : 1294582321 } } }
{ "ts" : { "t" : 1294582140000, "i" : 18 }, "op" : "u", "ns" : "mixi_top_city.building_03", "o2" : { "_id" : "9882403_353_28_20" }, "o" : { "$set" : { "ntime" : 1294600141 } } }
{ "ts" : { "t" : 1294582140000, "i" : 19 }, "op" : "u", "ns" : "mixi_top_city.building_24", "o2" : { "_id" : "4162924_365_32_28" }, "o" : { "$set" : { "ntime" : 1294582321 } } }
{ "ts" : { "t" : 1294582141000, "i" : 1 }, "op" : "u", "ns" : "mixi_top_city.building_49", "o2" : { "_id" : "32797749_285_28_30" }, "o" : { "$set" : { "ntime" : 1294583341 } } }
{ "ts" : { "t" : 1294582141000, "i" : 2 }, "op" : "u", "ns" : "mixi_top_city.building_50", "o2" : { "_id" : "33768850_425_28_32" }, "o" : { "$set" : { "ntime" : 1294582561 } } }
{ "ts" : { "t" : 1294582141000, "i" : 3 }, "op" : "u", "ns" : "mixi_top_city.building_35", "o2" : { "_id" : "28235635_333_28_36" }, "o" : { "$set" : { "ntime" : 1294582741 } } }
{ "ts" : { "t" : 1294582141000, "i" : 4 }, "op" : "u", "ns" : "mixi_top_city.building_04", "o2" : { "_id" : "25178304_3_32_28" }, "o" : { "$set" : { "ntime" : 1294594141 } } }
{ "ts" : { "t" : 1294582141000, "i" : 5 }, "op" : "u", "ns" : "mixi_top_city.building_18", "o2" : { "_id" : "7304918_445_32_26" }, "o" : { "$set" : { "ntime" : 1294582321 } } }
{ "ts" : { "t" : 1294582141000, "i" : 6 }, "op" : "u", "ns" : "mixi_top_city.building_93", "o2" : { "_id" : "5003293_453_20_24" }, "o" : { "$set" : { "status" : 1 } } }
{ "ts" : { "t" : 1294582141000, "i" : 7 }, "op" : "u", "ns" : "mixi_top_city.building_59", "o2" : { "_id" : "19601459_485_28_30" }, "o" : { "$set" : { "ntime" : 1294582741 } } }
{ "ts" : { "t" : 1294582141000, "i" : 8 }, "op" : "u", "ns" : "mixi_top_city.building_47", "o2" : { "_id" : "23744647_273_22_46" }, "o" : { "$set" : { "ntime" : 1294582741 } } }
{ "ts" : { "t" : 1294582141000, "i" : 9 }, "op" : "u", "ns" : "mixi_top_city.building_50", "o2" : { "_id" : "3549050_451_20_30" }, "o" : { "$set" : { "ntime" : 1294583041 } } }
{ "ts" : { "t" : 1294582141000, "i" : 10 }, "op" : "d", "ns" : "mixi_top_city.building_77", "b" : true, "o" : { "_id" : "8577977_215_44_38" } }
{ "ts" : { "t" : 1294582141000, "i" : 11 }, "op" : "i", "ns" : "mixi_top_city.building_77", "o" : { "_id" : "8577977_215_44_38", "uid" : "8577977", "x" : 44, "y" : 38, "pos" : 1, "btime" : 1293955498, "ntime" : 1294486420, "bid" : 18, "extprop" : 0, "status" : 0, "ucid" : 215 } }
{ "ts" : { "t" : 1294582141000, "i" : 12 }, "op" : "u", "ns" : "mixi_top_city.building_89", "o2" : { "_id" : "21405489_541_20_24" }, "o" : { "$set" : { "status" : 1 } } }
{ "ts" : { "t" : 1294582141000, "i" : 13 }, "op" : "u", "ns" : "mixi_top_city.building_60", "o2" : { "_id" : "6479060_395_16_32" }, "o" : { "$set" : { "ntime" : 1294582321 } } }
{ "ts" : { "t" : 1294582141000, "i" : 14 }, "op" : "u", "ns" : "mixi_top_city.building_38", "o2" : { "_id" : "12696438_1037_28_40" }, "o" : { "$set" : { "ntime" : 1294583042 } } }
has more
>

Oplog日志中:

ts:Timestamp 这个操作的时间戳

op:operation 操作

i – insert
d – delete
u – update
c – command
n – no-op

ns:Namespace也就是操作的collection name

o:Document

 

查看master的Oplog信息:

[root@tc-03 cacti]# mongo
MongoDB shell version: 1.6.4
connecting to: test
> db.printReplicationInfo();
configured oplog size:   7503.049113600001MB
log length start to end: 3566227secs (990.62hrs)
oplog first event time:  Tue Jan 11 2011 12:17:03 GMT+0900 (KST)
oplog last event time:   Mon Feb 21 2011 18:54:10 GMT+0900 (KST)
now:                     Mon Feb 21 2011 18:54:10 GMT+0900 (KST)

 

查看slave的同步状态:

> db.printSlaveReplicationInfo()
source:   192.168.8.173
         syncedTo: Mon Feb 21 2011 18:55:19 GMT+0900 (KST)
                 = 31secs ago (0.01hrs)

 

网上已经有人写好了mongodb的nagios监控脚本,参考:https://github.com/mzupan/nagios-plugin-mongodb/blob/master/README.md

1) 先安装git

yum install git

2) 下载脚本

cd /etc/nagios/command

git clone git://github.com/mzupan/nagios-plugin-mongodb.git

cd nagios-plugin-mongodb/
chmod 755 check_mongodb.py

如果执行报下面的错误:

# ./check_mongodb.py –help
need to install pymongo

需要安装pymongo:

git clone git://github.com/mongodb/mongo-python-driver.git pymongo
cd pymongo/
python setup.py install

3) 修改nagios配置,加入这个命令

vi objects/commands.cfg
# ‘check_mongodb’ command definition
define command {
        command_name    check_mongodb
        command_line    /etc/nagios/command/nagios-plugin-mongodb/check_mongodb.py -H $HOSTADDRESS$ -A $ARG1$ -P $ARG2$ -W $ARG3$ -C $ARG4$
        }

4) 加入mongo监控的配置

vi hosts.cfg
define service{
        use                             generic-service         ; Name of service template to use
        host_name                       monodb_host
        service_description             mongodb
        check_command                   check_mongodb!connect!27017!10!30
        notifications_enabled           1
        }
      

5) 没错的话,重载nagios就行了。

nagios -v /etc/nagios/nagios.cfg
service nagios reload

       我们有项目用到了MongoDB,在正式运营时数据需要做实时备份,而目前资源也有限,只有两台机器用于MongoDB,所以选用MongoDB的Master-Slave Replication来实现实时备份,其实我是比较看好Replica Sets的,因为其可以实现自动切换,但我目前因为机器少只能做罢。

两台机器一台为Master(192.168.1.173),一台为Slave(192.168.1.174),系统环境为:centos linux 5.5。

1)在两台机器上都安装MongoDB

我们直接用官方编译好的rpm包

先增加mongodb的rpm库:
vi /etc/yum.repos.d/10gen.repo

[10gen]
name=10gen Repository
baseurl=http://downloads.mongodb.org/distros/centos/5.4/os/x86_64/
gpgcheck=0

再yum安装:

yum -y install mongo-stable mongo-stable-server

2)配置Master:

vi /etc/mongod.conf

增加下面几行:
rest = true
master = true

启动:
service mongod start

3)配置Slave:

vi /etc/mongod.conf

增加下面几行:

rest = true
slave = true
source = 192.168.1.174
autoresync = true

启动:
service mongod start

4)查看和测试
查看相关的状态
db.printReplicationInfo()

查看是否为Master:
db.isMaster();

测试:
在Master上增加一个Collection
db.createCollection("mycoll2", {capped:true, size:100000})

然后再在Slave上查看
show collections

如果能看到刚刚新增的mycoll2,说明成功了。

4)故障测试

A. Slave机器出了问题怎么办?

按照上面的3)中的步骤,重做一遍就是了。

B. Master机器出问题怎么办?

如果Master机器挂了,那么我们可以先把Slave改成Master让其提供服务:

在Slave上先停止mongod:

service mongod stop

再删除本地数据库,因为slave的相关信息存在这里面了。
cd /var/lib/mongo

rm -rf local.*

在配置文件内把slave改成master:

vi /etc/mongod.conf

删掉下面几行:
slave = true
source = 192.168.1.174
autoresync = true

增加:
master = true

最后再启动mongod:

service mongod start

2010年8月5日,Mongodb 1.6正式发布了,这个版本增加和改进了很多功能,我了解的几个比较大的改进在:

1) Mongodb存储文件申请磁盘空间的方式做了改进。在mongodb  1.4的时候是按128M,256M,512M,1024M,2048M这样的方式申请磁盘空间的;而在mongodb  1.6中,已经是动态小量的申请磁盘空间了。

2) 增加了$or等查询操作符,这在mongodb 1.4的时候是没有的。

3) 改进和提高了并发性能。

4) Replication的同步方面做了改进。

5) etc…

详细的changelog可以看:http://jira.mongodb.org/browse/SERVER?report=com.atlassian.jira.plugin.system.project:changelog-panel

在我发表这篇文章时,发现Mongodb 1.6.1也已经发布了,主要是修复了一些bug。

居然说性能得到了提高,那么我们就对Mongodb 1.6和Mongodb 1.4分别做了一个性能测试,想对比下看看Mongodb 1.6性能到底比Mongodb 1.4提高了多少。

测试机器为一台普通台式机,安装在64位centos linux 5.4系统。cpu为Intel E7500,内存为2G,单个普通500G硬盘。

测试程序为自己用java写的,可在此下载:http://farmerluo.googlecode.com/files/mongotest-0.4.rar

测试程序每次测试都会insert 100万条记录(如10并发测试,每并发insert 10万条记录;20并发测试,每并发5万条记录…),每记录大小为1KB,然后再逐条update所有记录,最后逐条select出来。

测试程序是在本机跑的,所以本次测试忽略网络延时。好,下面我们看测试结果:

下面图中横轴10…100是指并发测试的并发线程。

在insert测试内,Mongodb1.4和Mongodb1.6平分秋色,基本上没有区别。虽然在mongodb 1.6中对申请磁盘空间方式做了改进,但对性能的提升没有体现出来。

随着并发的增加,性能快速下降的问题也没有得到改进。

在update方面,性能提升显著,合计有75%的性能提高。而且表现平稳,随着并发的增加,性能稳定。非常不错。


select方面表现也不错,合计有83%性能提高。在并发线程小时尤其明显。

通过上面几个方面测试的结果表明,Mongodb 1.6是还是非常值得我们升级的,不但增加了一些新功能,性能也得到了很大的提高。

mongodb提供了两个命令来备份(mongodump )和恢复(mongorestore )数据库。

1.备份(mongodump )

用法 :
[root@web3 3]# mongodump –help
options:
–help                   produce help message
-v [ –verbose ]         be more verbose (include multiple times for more
verbosity e.g. -vvvvv)
-h [ –host ] arg        mongo host to connect to ("left,right" for pairs)
-d [ –db ] arg          database to use
-c [ –collection ] arg  collection to use (some commands)
-u [ –username ] arg    username
-p [ –password ] arg    password
–dbpath arg             directly access mongod data files in the given path,
instead of connecting to a mongod instance – needs
to lock the data directory, so cannot be used if a
mongod is currently accessing the same path
–directoryperdb         if dbpath specified, each db is in a separate
directory
-o [ –out ] arg (=dump) output directory

例子:

[root@web3 ~]# mongodump -h 192.168.1.103 -d citys -o /backup/mongobak/3
connected to: 192.168.1.103
DATABASE: citys  to     /backup/mongobak/3/citys
citys.building to /backup/mongobak/3/citys/building.bson
13650 objects
citys.system.indexes to /backup/mongobak/3/citys/system.indexes.bson
1 objects

备份出来的数据是二进制的,已经经过压缩。比实际数据库要小很多,我的数据库显示占用了260多M,备份后只有2M。

2.恢复(mongorestore )

用法:
[root@web3 3]# mongorestore –help
usage: mongorestore [options] [directory or filename to restore from]
options:
–help                  produce help message
-v [ –verbose ]        be more verbose (include multiple times for more
verbosity e.g. -vvvvv)
-h [ –host ] arg       mongo host to connect to ("left,right" for pairs)
-d [ –db ] arg         database to use
-c [ –collection ] arg collection to use (some commands)
-u [ –username ] arg   username
-p [ –password ] arg   password
–dbpath arg            directly access mongod data files in the given path,
instead of connecting to a mongod instance – needs to
lock the data directory, so cannot be used if a
mongod is currently accessing the same path
–directoryperdb        if dbpath specified, each db is in a separate
directory
–drop                  drop each collection before import
–objcheck              validate object before inserting

–drop参数可以在导入之前把collection先删掉。

例子:

[root@web3 3]# mongorestore -h 127.0.0.1 –directoryperdb /backup/mongobak/3/        
connected to: 127.0.0.1
/backup/mongobak/3/citys/building.bson
going into namespace [citys.building]
13667 objects
/backup/mongobak/3/citys/system.indexes.bson
going into namespace [citys.system.indexes]
1 objects

另外mongodb还提供了mongoexport 和 mongoimport 这两个命令来导出或导入数据,导出的数据是json格式的。也可以实现备份和恢复的功能。

例:

mongoexport -d mixi_top_city_prod -c building_45 -q ‘{ "uid" : "10832545" }’ > mongo_10832545.bson

mongoimport -d mixi_top_city -c building_45 –file mongo_10832545.bson

 

 

 

   在之前的《MongoDB分布式部署》中已经提到,同一组数据库服务器上的数据是支持复制的。mongodb支持两种方式的数据复制,简单的主从配置和互为主从的配置。

MongoDB分布式部署之分片配置可见http://hi.baidu.com/lzpsky/blog/item/644e083d6bbd920cbaa16793.html

   一、主从配置(Master Slave)

主从数据库需要两个数据库节点即可,一主一从(并不一定非得两台独立的服务器,可使用–dbpath参数指定数据库目录)。
$ bin/mongod –master [–dbpath /data/masterdb/]  

于是,主服务器进程就会创建一个local.oplog.$main数据集,即"transaction log",以记录从服务器需要的操作队列信息。

配置一个从数据库节点:

$ bin/mongod –slave –source <masterhostname>[:<port>] [–dbpath /data/slavedb/]

主节点的信息会存放在从节点的 local.sources数据集中,也可以不指定–source参数,而是往local.sources中增加一条包含主节点信息的记录。

$ bin/mongo <slavehostname>/local
> db.sources.find();      // confirms the collection is empty. then:
> db.sources.insert( { host: <masterhostname> } );
host:<masterhostname> 主节点的ip地址或域名全称,可跟上:port指定端口。

如果指定了only: databasename (optional) ,表示只有指定的数据库才复制,注:v1.2.1+修复了only的一个bug。

一个从节点可以有多个主节点,这种情况下,local.sources中会有多条配置信息。

  一台服务器可以同时即为主也为从。如果一台从节点与主节点不同步,比如从节点的数据更新远远跟不上主节点或者从节点中断之后重启但主节点中相关的数据更新日志却不可用了。这种情况下,复制操作将会终止,需要管理者的介入,看是否默认需要重启复制操作。管理者可以使用{resync:1} 命令重启复制操作,可选命令行参数 --autoresync可使从节点在不同步情况发生10秒钟之后,自动重启复制操作。如果指定了--autoresync参数,从节点在10分钟以内自动重新同步数据的操作只会执行一次。

--oplogSize命令行参数(与--master一同使用)配置用于存储给从节点可用的更新信息占用的磁盘空间(M为单位),如果不指定这个参数,默认大小为当前可用磁盘空间的5%(64位机器最小值为1G,32位机器为50M)。

安全性方面:

$ dbshell <slavehostname>/admin -u <existingadminusername> -p<adminpassword>
> use local
> db.addUser(‘repl’, <replpassword>);
^c
$ dbshell <masterhostname>/admin -u <existingadminusername> -p<adminpassword>
> use local
> db.addUser(‘repl’, <replpassword>);
  
二、互为主从(
Replica Pairs

数据库自动协调某个时间点上的主从关系。开始的时候,数据库会判断哪个是从哪个是主,一旦主服务器负载过高,另一台就会自动成为主服务器。

$ ./mongod –pairwith <remoteserver> –arbiter <arbiterserver>

remoteserver组中的其他服务器host,可加:port指定端口。
arbiterserver 仲裁(arbiter )的host,也可指定端口。仲裁是一台mongodb服务器,用于协助判断某个时间点上的数据库主从关系。如果同组服务器在同一个交换机或相同的ec2可用区域内,就没必要使用仲裁了。如果同组服务器之间不能通信,可是使用运行在第三方机器上的仲裁,使用“抢七”方式有效地敲定主服务器,也可不使用仲裁,这样所有的服务器都假定是主服务器状态,可通过命令人工检测当前哪台数据库是主数据库:

$ ./mongo
> db.$cmd.findOne({ismaster:1});
{ "ismaster" : 0.0 , "remote" : "192.168.58.1:30001" , "ok" : 1.0 }

一致性:故障转移机制只能够保障组中的数据库上的数据的最终一致性。如果机器L是主服务器,然后挂了,那么发生在它身上的最后几秒钟的操作信息就到达不了机器R,那么机器R在机器L恢复之前是不能执行这些操作的。

安全性:同主从的操作相同。

数据库服务器替换。当一台服务器失败了,系统能自动在线恢复。但当一台机器彻底挂了,就需要替换机器,而替换机器一开始是没有数据的,怎么办?以下会解释如何替换一组服务器中的一台机器。

假设nodes(n1,n2)中的n2挂了,需要变成nodes(n1,n3)。
1、假设n2彻底挂了下线了,不能在线恢复。
2、需要告诉n1,你的搭档已经不是n2了而是n3。可使用replacepeer 命令,检测该操作的返回值以确保操作成功。

n1> ./mongo n1/admin
> db.$cmd.findOne({replacepeer:1});
{
"info" : "adjust local.sources hostname; db restart now required"
"ok" : 1.0
}

3、使用以下命令重启n1。
n1> ./mongod –pairwith n3 –arbiter <arbiterserver>

4、启动n3。
n3> ./mongod –pairwith n1 –arbiter <arbiterserver>

注意的是,n3在与n1数据完全同步之前不能接收作为主节点的任何操作。
如果从节点设置了ok标志(db.getMongo().setSlaveOk() ),就可以查询从节点了。

三、配置案例

《MongoDB分布式部署之分片配置》的第六个模块——案例部分,有详解。

四、java案例

1、链接mongodb:
ServerAddress right = new ServerAddress("10.13.127.212", 18020);
ServerAddress left = new ServerAddress("10.13.127.211", 18020);
Mongo mongo = new Mongo(right, left);

DB db = mongo.getDB("test");
db.authenticate("test", "test".toCharArray());
2、插入:
BasicDBObject dbObject = new BasicDBObject();
dbObject.put("id", i);
dbObject.put("time", System.currentTimeMillis());

coll.insert(dbObject);
3、更新
coll.update(new BasicDBObject("_id", id),  getDBObjectByLabel(mongoLabel));
4、查询,查询条件的表达式文档
BasicDBObject dbObject = new BasicDBObject();
dbObject.put("id", 1);    //增加查询条件
BasicDBObject sortObject = new BasicDBObject(LabelConstant.ORDER_BY,LabelConstant.ORDER_DESC);

DBCursor dbCursor = coll.find(dbObject);  //不分页不排序
//cursor = coll.find(doc).skip((pageNo – 1) * pageSize).limit(pageSize).sort(sortObject);  //分页排序
DBObject result = dbCursor.next();  //取数据

在前面的文章“mongodb 查询的语法”里,我介绍了Mongodb的常用查询语法,Mongodb的update操作也有点复杂,我结合自己的使用经验,在这里介绍一下,给用mongodb的朋友看看,也方便以后自己用到的时候查阅:

注:在这篇文章及上篇文章内讲的语法介绍都是在mongodb shell环境内的,和真正运用语言编程(如java,php等)使用时,在使用方法上会有一些差别,但语法(如查询条件,$in,$inc等)是一样的。

本文是参考官方文档来介绍的,之所以有官方文档还要在这介绍,一方面是就当翻译,毕竟每次要用时去看英文文档比较累,第二是官方文档讲解比较简单,有时光看官方文档不好理解,我在实际操作的情况下可以做些补充。

好了,不多说了,下面正式开始:

mongodb更新有两个命令:

1).update()命令

db.collection.update( criteria, objNew, upsert, multi )

criteria : update的查询条件,类似sql update查询内where后面的
objNew   : update的对象和一些更新的操作符(如$,$inc…)等,也可以理解为sql update查询内set后面的
upsert   : 这个参数的意思是,如果不存在update的记录,是否插入objNew,true为插入,默认是false,不插入。
multi    : mongodb默认是false,只更新找到的第一条记录,如果这个参数为true,就把按条件查出来多条记录全部更新。

例:
db.test0.update( { "count" : { $gt : 1 } } , { $set : { "test2" : "OK"} } ); 只更新了第一条记录
db.test0.update( { "count" : { $gt : 3 } } , { $set : { "test2" : "OK"} },false,true ); 全更新了
db.test0.update( { "count" : { $gt : 4 } } , { $set : { "test5" : "OK"} },true,false ); 只加进去了第一条
db.test0.update( { "count" : { $gt : 5 } } , { $set : { "test5" : "OK"} },true,true ); 全加进去了
db.test0.update( { "count" : { $gt : 15 } } , { $inc : { "count" : 1} },false,true );全更新了
db.test0.update( { "count" : { $gt : 10 } } , { $inc : { "count" : 1} },false,false );只更新了第一条

2).save()命令

db.collection.save( x )

x就是要更新的对象,只能是单条记录。

如果在collection内已经存在一个和x对象相同的"_id"的记录。mongodb就会把x对象替换collection内已经存在的记录,否则将会插入x对象,如果x内没有_id,系统会自动生成一个再插入。相当于上面update语句的upsert=true,multi=false的情况。

例:
db.test0.save({count:40,test1:"OK"}); #_id系统会生成
db.test0.save({_id:40,count:40,test1:"OK"}); #如果test0内有_id等于40的,会替换,否则插入。

mongodb的更新操作符:

1) $inc

用法:{ $inc : { field : value } }

意思对一个数字字段field增加value,例:

> db.test0.find( { "_id" : 15 } );
{ "_id" : { "floatApprox" : 15 }, "count" : 16, "test1" : "TESTTEST", "test2" : "OK", "test3" : "TESTTEST", "test4" : "OK", "test5" : "OK" }

> db.test0.update( { "_id" : 15 } , { $inc : { "count" : 1 } } );
> db.test0.find( { "_id" : 15 } );
{ "_id" : { "floatApprox" : 15 }, "count" : 17, "test1" : "TESTTEST", "test2" : "OK", "test3" : "TESTTEST", "test4" : "OK", "test5" : "OK" }

> db.test0.update( { "_id" : 15 } , { $inc : { "count" : 2 } } );
> db.test0.find( { "_id" : 15 } );
{ "_id" : { "floatApprox" : 15 }, "count" : 19, "test1" : "TESTTEST", "test2" : "OK", "test3" : "TESTTEST", "test4" : "OK", "test5" : "OK" }

> db.test0.update( { "_id" : 15 } , { $inc : { "count" : -1 } } );
> db.test0.find( { "_id" : 15 } );
{ "_id" : { "floatApprox" : 15 }, "count" : 18, "test1" : "TESTTEST", "test2" : "OK", "test3" : "TESTTEST", "test4" : "OK", "test5" : "OK" }

2) $set

用法:{ $set : { field : value } }

就是相当于sql的set field = value,全部数据类型都支持$set。例:
> db.test0.update( { "_id" : 15 } , { $set : { "test1" : "testv1","test2" : "testv2","test3" : "testv3","test4" : "testv4" } } );
> db.test0.find( { "_id" : 15 } );
{ "_id" : { "floatApprox" : 15 }, "count" : 18, "test1" : "testv1", "test2" : "testv2", "test3" : "testv3", "test4" : "testv4", "test5" : "OK" }

3) $unset

用法:{ $unset : { field : 1} }

顾名思义,就是删除字段了。例:
> db.test0.update( { "_id" : 15 } , { $unset : { "test1":1 } } );
> db.test0.find( { "_id" : 15 } );
{ "_id" : { "floatApprox" : 15 }, "count" : 18, "test2" : "testv2", "test3" : "testv3", "test4" : "testv4", "test5" : "OK" }

> db.test0.update( { "_id" : 15 } , { $unset : { "test2": 0 } } );
> db.test0.find( { "_id" : 15 } );
{ "_id" : { "floatApprox" : 15 }, "count" : 18, "test3" : "testv3", "test4" : "testv4", "test5" : "OK" }

> db.test0.update( { "_id" : 15 } , { $unset : { "test3":asdfasf } } );
Fri May 14 16:17:38 JS Error: ReferenceError: asdfasf is not defined (shell):0

> db.test0.update( { "_id" : 15 } , { $unset : { "test3":"test" } } );
> db.test0.find( { "_id" : 15 } );
{ "_id" : { "floatApprox" : 15 }, "count" : 18, "test4" : "testv4", "test5" : "OK" }

没看出field : 1里面的1是干什么用的,反正只要有东西就行。

4) $push

用法:{ $push : { field : value } }

把value追加到field里面去,field一定要是数组类型才行,如果field不存在,会新增一个数组类型加进去。例:

> db.test0.update( { "_id" : 15 } , { $set : { "test1" : ["aaa","bbb"] } } );
> db.test0.find( { "_id" : 15 } );
{ "_id" : { "floatApprox" : 15 }, "count" : 18, "test1" : [ "aaa", "bbb" ], "test4" : "testv4", "test5" : "OK" }

> db.test0.update( { "_id" : 15 } , { $push : { "test1": "ccc" } } );
> db.test0.find( { "_id" : 15 } );
{ "_id" : { "floatApprox" : 15 }, "count" : 18, "test1" : [ "aaa", "bbb", "ccc" ], "test4" : "testv4", "test5" : "OK" }

> db.test0.update( { "_id" : 15 } , { $push : { "test2": "ccc" } } );
> db.test0.find( { "_id" : 15 } );
{ "_id" : { "floatApprox" : 15 }, "count" : 18, "test1" : [ "aaa", "bbb", "ccc" ], "test2" : [ "ccc" ], "test4" : "testv4", "test5" : "OK" }

> db.test0.update( { "_id" : 15 } , { $push : { "test1": ["ddd","eee"] } } );
> db.test0.find( { "_id" : 15 } );
{ "_id" : { "floatApprox" : 15 }, "count" : 18, "test1" : [ "aaa", "bbb", "ccc", [ "ddd", "eee" ] ], "test2" : [ "ccc" ], "test4" : "testv4", "test5" : "OK" }5) $pushAll

用法:{ $pushAll : { field : value_array } }

同$push,只是一次可以追加多个值到一个数组字段内。例:

> db.test0.find( { "_id" : 15 } );
{ "_id" : { "floatApprox" : 15 }, "count" : 18, "test1" : [ "aaa", "bbb", "ccc", [ "ddd", "eee" ] ], "test2" : [ "ccc" ], "test4" : "testv4", "test5" : "OK" }

> db.test0.update( { "_id" : 15 } , { $pushAll : { "test1": ["fff","ggg"] } } );
> db.test0.find( { "_id" : 15 } );
{ "_id" : { "floatApprox" : 15 }, "count" : 18, "test1" : [ "aaa", "bbb", "ccc", [ "ddd", "eee" ], "fff", "ggg" ], "test2" : [ "ccc" ], "test4" : "testv4", "test5" : "OK" }

6)  $addToSet

用法:{ $addToSet : { field : value } }

增加一个值到数组内,而且只有当这个值不在数组内才增加。例:
> db.test0.update( { "_id" : 15 } , { $addToSet : { "test1": {$each : ["444","555"] } } } );
> db.test0.find( { "_id" : 15 } );
{ "_id" : { "floatApprox" : 15 }, "count" : 18, "test1" : [
"aaa",
"bbb",
"ccc",
[
"ddd",
"eee"
],
"fff",
"ggg",
[
"111",
"222"
],
"444",
"555"
], "test2" : [ "ccc" ], "test4" : "testv4", "test5" : "OK" }
> db.test0.update( { "_id" : 15 } , { $addToSet : { "test1": {$each : ["444","555"] } } } );
> db.test0.find( { "_id" : 15 } );
{ "_id" : { "floatApprox" : 15 }, "count" : 18, "test1" : [
"aaa",
"bbb",
"ccc",
[
"ddd",
"eee"
],
"fff",
"ggg",
[
"111",
"222"
],
"444",
"555"
], "test2" : [ "ccc" ], "test4" : "testv4", "test5" : "OK" }
> db.test0.update( { "_id" : 15 } , { $addToSet : { "test1": ["444","555"] } } );
> db.test0.find( { "_id" : 15 } );
{ "_id" : { "floatApprox" : 15 }, "count" : 18, "test1" : [
"aaa",
"bbb",
"ccc",
[
"ddd",
"eee"
],
"fff",
"ggg",
[
"111",
"222"
],
"444",
"555",
[
"444",
"555"
]
], "test2" : [ "ccc" ], "test4" : "testv4", "test5" : "OK" }
> db.test0.update( { "_id" : 15 } , { $addToSet : { "test1": ["444","555"] } } );
> db.test0.find( { "_id" : 15 } );
{ "_id" : { "floatApprox" : 15 }, "count" : 18, "test1" : [
"aaa",
"bbb",
"ccc",
[
"ddd",
"eee"
],
"fff",
"ggg",
[
"111",
"222"
],
"444",
"555",
[
"444",
"555"
]
], "test2" : [ "ccc" ], "test4" : "testv4", "test5" : "OK" }

7) $pop

删除数组内的一个值

用法:
删除最后一个值:{ $pop : { field : 1 } }删除第一个值:{ $pop : { field : -1 } }

注意,只能删除一个值,也就是说只能用1或-1,而不能用2或-2来删除两条。mongodb 1.1及以后的版本才可以用,例:
> db.test0.find( { "_id" : 15 } );
{ "_id" : { "floatApprox" : 15 }, "count" : 18, "test1" : [
"bbb",
"ccc",
[
"ddd",
"eee"
],
"fff",
"ggg",
[
"111",
"222"
],
"444"
], "test2" : [ "ccc" ], "test4" : "testv4", "test5" : "OK" }
> db.test0.update( { "_id" : 15 } , { $pop : { "test1": -1 } } );
> db.test0.find( { "_id" : 15 } );
{ "_id" : { "floatApprox" : 15 }, "count" : 18, "test1" : [
"ccc",
[
"ddd",
"eee"
],
"fff",
"ggg",
[
"111",
"222"
],
"444"
], "test2" : [ "ccc" ], "test4" : "testv4", "test5" : "OK" }
> db.test0.update( { "_id" : 15 } , { $pop : { "test1": 1 } } );
> db.test0.find( { "_id" : 15 } );
{ "_id" : { "floatApprox" : 15 }, "count" : 18, "test1" : [ "ccc", [ "ddd", "eee" ], "fff", "ggg", [ "111", "222" ] ], "test2" : [ "ccc" ], "test4" : "testv4",
"test5" : "OK" }

8) $pull

用法:$pull : { field : value } }

从数组field内删除一个等于value值。例:
> db.test0.find( { "_id" : 15 } );
{ "_id" : { "floatApprox" : 15 }, "count" : 18, "test1" : [ "ccc", [ "ddd", "eee" ], "fff", "ggg", [ "111", "222" ] ], "test2" : [ "ccc" ], "test4" : "testv4",
"test5" : "OK" }

> db.test0.update( { "_id" : 15 } , { $pull : { "test1": "ggg" } } );
> db.test0.find( { "_id" : 15 } );
{ "_id" : { "floatApprox" : 15 }, "count" : 18, "test1" : [ "ccc", [ "ddd", "eee" ], "fff", [ "111", "222" ] ], "test2" : [ "ccc" ], "test4" : "testv4", "test5"
: "OK" }

9) $pullAll

用法:{ $pullAll : { field : value_array } }

同$pull,可以一次删除数组内的多个值。例:
> db.test0.find( { "_id" : 15 } );
{ "_id" : { "floatApprox" : 15 }, "count" : 18, "test1" : [ "ccc", [ "ddd", "eee" ], "fff", [ "111", "222" ] ], "test2" : [ "ccc" ], "test4" : "testv4", "test5"
: "OK" }

> db.test0.update( { "_id" : 15 } , { $pullAll : { "test1": [ "ccc" , "fff" ] } } );
> db.test0.find( { "_id" : 15 } );
{ "_id" : { "floatApprox" : 15 }, "count" : 18, "test1" : [ [ "ddd", "eee" ], [ "111", "222" ] ], "test2" : [ "ccc" ], "test4" : "testv4", "test5" : "OK" }

10) $ 操作符

$是他自己的意思,代表按条件找出的数组里面某项他自己。呵呵,比较坳口。看一下官方的例子:

> t.find()
{ "_id" : ObjectId("4b97e62bf1d8c7152c9ccb74"), "title" : "ABC", "comments" : [ { "by" : "joe", "votes" : 3 }, { "by" : "jane", "votes" : 7 } ] }

> t.update( {‘comments.by’:’joe’}, {$inc:{‘comments.$.votes’:1}}, false, true )

> t.find()
{ "_id" : ObjectId("4b97e62bf1d8c7152c9ccb74"), "title" : "ABC", "comments" : [ { "by" : "joe", "votes" : 4 }, { "by" : "jane", "votes" : 7 } ] }

需要注意的是,$只会应用找到的第一条数组项,后面的就不管了。还是看例子:

> t.find();
{ "_id" : ObjectId("4b9e4a1fc583fa1c76198319"), "x" : [ 1, 2, 3, 2 ] }
> t.update({x: 2}, {$inc: {"x.$": 1}}, false, true);
> t.find();

还有注意的是$配合$unset使用的时候,会留下一个null的数组项,不过可以用{$pull:{x:null}}删除全部是null的数组项。例:
> t.insert({x: [1,2,3,4,3,2,3,4]})
> t.find()
{ "_id" : ObjectId("4bde2ad3755d00000000710e"), "x" : [ 1, 2, 3, 4, 3, 2, 3, 4 ] }
> t.update({x:3}, {$unset:{"x.$":1}})
> t.find()
{ "_id" : ObjectId("4bde2ad3755d00000000710e"), "x" : [ 1, 2, null, 4, 3, 2, 3, 4 ] }

{ "_id" : ObjectId("4b9e4a1fc583fa1c76198319"), "x" : [ 1, 3, 3, 2 ] }

满足海量存储需求和访问的面向文档的数据库:MongoDB,CouchDB
MongoDB
Nice, I like it very much.

面向文档的非关系数据库主要解决的问题不是高性能的并发读写,而是保证海量数据存储的同时,具有良好的查询性能。MongoDB是用C++开发的,而CouchDB则是Erlang开发的:

1、MongoDB
MongoDB是一个介于关系数据库和非关系数据库之间的产品,是非关系数据库当中功能最丰富,最像关系数据库的。他支持的数据结构非常松散,是类似json的bjson格式,因此可以存储比较复杂的数据类型。Mongo最大的特点是他支持的查询语言非常强大,其语法有点类似于面向对象的查询语言,几乎可以实现类似关系数据库单表查询的绝大部分功能,而且还支持对数据建立索引。

Mongo主要解决的是海量数据的访问效率问题,根据官方的文档,当数据量达到50GB以上的时候,Mongo的数据库访问速度是MySQL的 10倍以上。Mongo的并发读写效率不是特别出色,根据官方提供的性能测试表明,大约每秒可以处理0.5万-1.5次读写请求。对于Mongo的并发读写性能,我(robbin)也打算有空的时候好好测试一下。

因为Mongo主要是支持海量数据存储的,所以Mongo还自带了一个出色的分布式文件系统GridFS,可以支持海量的数据存储,但我也看到有些评论认为GridFS性能不佳,这一点还是有待亲自做点测试来验证了。

最后由于Mongo可以支持复杂的数据结构,而且带有强大的数据查询功能,因此非常受到欢迎,很多项目都考虑用MongoDB来替代MySQL来实现不是特别复杂的Web应用,比方说why we migrated from MySQL to MongoDB就是一个真实的从MySQL迁移到MongoDB的案例,由于数据量实在太大,所以迁移到了Mongo上面,数据查询的速度得到了非常显著的提升。

MongoDB也有一个ruby的项目MongoMapper,是模仿Merb的DataMapper编写的MongoDB的接口,使用起来非常简单,几乎和DataMapper一模一样,功能非常强大易用。

MongoDB语法:
启动服务
mongod.exe –dbpath F:DataBaseMongoDBdb
–dbpath 数据文件存放路径
–port 数据服务端口
启动客户端
mongo.exe cclove
cclove  所连接的数据库名称

数据库操作语法
mongo –path
db.AddUser(username,password)  添加用户
db.auth(usrename,password)     设置数据库连接验证
db.cloneDataBase(fromhost)     从目标服务器克隆一个数据库
db.commandHelp(name)           returns the help for the command
db.copyDatabase(fromdb,todb,fromhost)  复制数据库fromdb—源数据库名称,todb—目标数据库名称,fromhost—源数据库服务器地址
db.createCollection(name,{size:3333,capped:333,max:88888})  创建一个数据集,相当于一个表
db.currentOp()                 取消当前库的当前操作
db.dropDataBase()              删除当前数据库
db.eval(func,args)             run code server-side
db.getCollection(cname)        取得一个数据集合,同用法:db[‘cname’] or db.cname
db.getCollenctionNames()       取得所有数据集合的名称列表
db.getLastError()              返回最后一个错误的提示消息
db.getLastErrorObj()           返回最后一个错误的对象
db.getMongo()                  取得当前服务器的连接对象get the server connection object
db.getMondo().setSlaveOk()     allow this connection to read from then nonmaster membr of a replica pair
db.getName()                   返回当操作数据库的名称
db.getPrevError()              返回上一个错误对象
db.getProfilingLevel()         ?什么等级
db.getReplicationInfo()        ?什么信息
db.getSisterDB(name)           get the db at the same server as this onew
db.killOp()                    停止(杀死)在当前库的当前操作
db.printCollectionStats()      返回当前库的数据集状态
db.printReplicationInfo()
db.printSlaveReplicationInfo()
db.printShardingStatus()       返回当前数据库是否为共享数据库
db.removeUser(username)        删除用户
db.repairDatabase()            修复当前数据库
db.resetError()               
db.runCommand(cmdObj)          run a database command. if cmdObj is a string, turns it into {cmdObj:1}
db.setProfilingLevel(level)    0=off,1=slow,2=all
db.shutdownServer()            关闭当前服务程序
db.version()                   返回当前程序的版本信息

数据集(表)操作语法
db.linlin.find({id:10})          返回linlin数据集ID=10的数据集
db.linlin.find({id:10}).count()  返回linlin数据集ID=10的数据总数
db.linlin.find({id:10}).limit(2) 返回linlin数据集ID=10的数据集从第二条开始的数据集
db.linlin.find({id:10}).skip(8)  返回linlin数据集ID=10的数据集从0到第八条的数据集
db.linlin.find({id:10}).limit(2).skip(8)  返回linlin数据集ID=1=的数据集从第二条到第八条的数据
db.linlin.find({id:10}).sort()   返回linlin数据集ID=10的排序数据集
db.linlin.findOne([query])       返回符合条件的一条数据
db.linlin.getDB()                返回此数据集所属的数据库名称
db.linlin.getIndexes()           返回些数据集的索引信息
db.linlin.group({key:…,initial:…,reduce:…[,cond:…]})
db.linlin.mapReduce(mayFunction,reduceFunction,<optional params>)
db.linlin.remove(query)                      在数据集中删除一条数据
db.linlin.renameCollection(newName)          重命名些数据集名称
db.linlin.save(obj)                          往数据集中插入一条数据
db.linlin.stats()                            返回此数据集的状态
db.linlin.storageSize()                      返回此数据集的存储大小
db.linlin.totalIndexSize()                   返回此数据集的索引文件大小
db.linlin.totalSize()                        返回些数据集的总大小
db.linlin.update(query,object[,upsert_bool]) 在此数据集中更新一条数据                        
db.linlin.validate()                         验证此数据集                                       
db.linlin.getShardVersion()                  返回数据集共享版本号

db.linlin.find({‘name’:’foobar’})    select * from linlin where name=’foobar’
db.linlin.find()                     select * from linlin
db.linlin.find({‘ID’:10}).count()    select count(*) from linlin where ID=10
db.linlin.find().skip(10).limit(20)  从查询结果的第十条开始读20条数据  select * from linlin limit 10,20  ———-mysql
db.linlin.find({‘ID’:{$in:[25,35,45]}})  select * from linlin where ID in (25,35,45)
db.linlin.find().sort({‘ID’:-1})      select * from linlin order by ID desc
db.linlin.distinct(‘name’,{‘ID’:{$lt:20}})   select distinct(name) from linlin where ID<20

db.linlin.group({key:{‘name’:true},cond:{‘name’:’foo’},reduce:function(obj,prev){prev.msum+=obj.marks;},initial:{msum:0}})
select name,sum(marks) from linlin group by name
db.linlin.find(‘this.ID<20’,{name:1})     select name from linlin where ID<20

db.linlin.insert({‘name’:’foobar’,’age’:25})  insert into linlin (‘name’,’age’) values(‘foobar’,25)
db.linlin.insert({‘name’:’foobar’,’age’:25,’email’:’cclove2@163.com’})

db.linlin.remove({})                   delete * from linlin
db.linlin.remove({‘age’:20})           delete linlin where age=20
db.linlin.remove({‘age’:{$lt:20}})     delete linlin where age<20
db.linlin.remove({‘age’:{$lte:20}})    delete linlin where age<=20
db.linlin.remove({‘age’:{$gt:20}})     delete linlin where age>20
db.linlin.remove({‘age’:{$gte:20}})    delete linlin where age>=20
db.linlin.remove({‘age’:{$ne:20}})     delete linlin where age!=20

db.linlin.update({‘name’:’foobar’},{$set:{‘age’:36}})  update linlin set age=36 where name=’foobar’
db.linlin.update({‘name’:’foobar’},{$inc:{‘age’:3}})   update linlin set age=age+3 where name=’foobar’

我在前面的文章内说过,准备做一个MongoDB与Mysql的并发对比测试,经过近一周的测试,已经完成,结果如下:

MongoDB与Mysql的并发对比测试曲线图:

我们大致总结一下:

通过数据和图表可以看到,在并发测试下,MongoDB对Mysql的优势没有在单用户那么大.了(可参考本空间前面的测试文章)。Mongodb的insert及update性能大约是Mysql的2~3倍,select方面MongoDB与Mysql相当。

测试环境:

CPU:Intel(R) Core(TM)2 Duo CPU     E7500  @ 2.93GHz
内存:4G
硬盘:普通SATA硬盘 500G
OS  :Centos Linux 5.4 X64

Mysql版本为:5.0.77 ,MongoDB为自己编译的1.40。

以上结果是由之前写的Java程序(http://farmerluo.googlecode.com/files/mongotest-0.4.rar)测试出来的,因为只有一台测试机器,测试程序是和mysql及MongoDB在同一台机器上运行测试的。