亿级别高性能MONGODB(2.4)优化心得

  1. 索引统一管理, 提高索引效率, 不乱建索引, 数据库死锁大多发生在更新索引上。
  2. 查询条件无顺序区别, 索引建立有顺序区别。 索引字段只能从前往后搜索, 不要指望{a,b,c}索引能覆盖b或bc, 必须有a才行, 因为a是入口!但是ab,ac都能覆盖。 find(c).sort(a)也能覆盖。 mongo一次查询只能命中单个索引。
  3. Create Queries that Ensure Selectivity, 官方文档原话, 什么叫selectivity? unique最selectivity, 只有一个或两个值的字段最inselectivity。建inselectivity的索引只会导致数据库性能下降, 毫无益处
  4. 延时数据与实时数据拆分, 写操作异常频繁的一定要做成延时数据, 否则会导致你所有实时数据都延时! 延时数据读写分离, Replica Sets就是拿来干这事的。
  5. 上亿数据写锁高, 必然是索引不合理, 合理索引2.4或更高版本能搞定10亿数据。
  6. 不要在长字段上建索引, 会吃光你的内存, 即使要建, 也做个映射字段, 比如拿长字段头5个字符 + 末尾5个字符拼接成10给字符的索引字段, 查长字段的时候把这个带上, 虽然可能一次命中几百条数据, 但是也比索引直接建在长字段上好很多。命中数据太多可以考虑增加索引字段长度。
  7. 分片会导致写性能下降, 大多数人认为可以接受。 但是肯定没有分库 + 连接池的效率高。 内存索引只能在一台机器上是这个问题的关键。单表按首字母分库不如分片, 速度快, 维护简单。
  8. 索引顺序对单键排序没有区别, 只有多键组合排序需要定义索引方向。
  9. $regex只有'^'开头的的正则式才能使用索引,大数据需要模糊搜索时需要建立full_text index
  10. 从库的写操作会被集群保留,但不会同步到主库,对从库进行写操作会导致主从数据不一致。

ThinkPHP下MongoDB的操作方法

https://github.com/liu21st/thinkphp/commits/master/ThinkPHP/Library/Think/Db/Driver/Mongo.class.php?author=vus520
向tp添加了一些mongodb操作方法,demo如下

Model

<?php
namespace Home\Model;
use Think\Model\MongoModel;
class UserActlogModel extends MongoModel
{
	
}

Controller

<?php
namespace Home\Controller;
use Think\Controller;
class IndexController extends Controller
{
	function index()
	{
		echo __CLASS__ .'\\'. __FUNCTION__;
	}

	function hello()
	{
		echo "Hello world";
	}

    public function mongo()
    {
    	$model = D("UserActLog");

    	//distinct查询语法,支持where、cache操作链
    	$result = $model->cache(false)->where( array('device.channelid'=>"10017") )->distinct('device.imei', array("day"=>"20140706"));
    	print_r(count($result));
    	print_r($model->_sql());
    	echo PHP_EOL;

    	//command查询语法
    	$data = $model->command(array('buildInfo'=>1));
    	//print_r($data);
    	print_r($model->_sql());
    	echo PHP_EOL;

    	$data = $model->status();
    	//print_r($data);
    	print_r($model->_sql());
    	echo PHP_EOL;

    	//MapReduce查询语法
    	$key    = array('device.channelid'=>1);
    	$init   = array('num'=>0);
    	$reduce = "function(obj, prev){prev.num++;}";
    	$option = array(
    			'table' => 'user_act_log',
    			'condition'=>array("day"=>"20140701"),
    	);
    	$result = $model->cache(true)->where( array("day"=>"20140708") )->group($key, $init, $reduce);
    	print_r(count($result));
    	print_r($model->_sql());
    	echo PHP_EOL;

    	//find查询语法,支持where、order、cache、field操作链
    	//field方法支持field("act,group"),field(array("act"=>true, "_id"=>false)),field(array("act", "group"))
    	$result = $model->where(array("day"=>"20140701"))->field(array("act"=>true, "group"=>true, "_id"=>false))->order('_id asc')->find();
    	print_r($result);
    	print_r($model->_sql());
    	echo PHP_EOL;

		//子查询OR逻辑
		$map['device.channel'] = "obx_03";
		$map['_logic'] = 'and';
		$map['_complex'] = array("act"=>"down", "group"=>"download", '_logic'=>"or");
		$result = $model->field(array("act"=>true, "group"=>true, "_id"=>false))->where($map)->limit(2)->select();
		print_r($result);
		print_r($model->_sql());
		echo PHP_EOL;

		//子查询OR逻辑
		$map['_complex'] = array('_logic'=>"or", array("act"=>"down"), array("group"=>"download"));
		$result = $model->where($map)->find();
		print_r($result);
		print_r($model->_sql());
		echo PHP_EOL;

		//子查询OR逻辑
    	$map = array();
    	$map['_complex'] = array('_logic'=>"or", array("act"=>"down"), array("group"=>"download"));
    	$result = $model->field(array("act"=>true, "group"=>true, "_id"=>false))->where($map)->find();
    	print_r($result);
    	print_r($model->_sql());
    	echo PHP_EOL;

    	//子查询 AND 逻辑
    	$map = array();
    	$map['_complex'] = array('_logic'=>"and", array("act"=>"down"), array("group"=>"download"));
    	$result = $model->field(array("act"=>true, "group"=>true, "_id"=>true))->where($map)->find();
    	print_r($result);
    	print_r($model->_sql());
    	echo PHP_EOL;

    	//子查询 nor 逻辑
    	$map = array();
    	$map['_complex'] = array('_logic'=>"nor", array("act"=>"down"), array("group"=>"download"));
    	$result = $model->field(array("act"=>true, "group"=>true, "_id"=>true))->where($map)->find();
    	print_r($result);
    	print_r($model->_sql());
    	echo PHP_EOL;

    	//$not查询
    	$map = array();
    	$map['day'] = '20140710';
    	$map['act'] = array('$exists'=>true, '$not'=>array('$in'=>array("click", "listview")) );
    	$map['_complex'] = array('_logic'=>"nor", array("act"=>"down"), array("group"=>"download"));
    	$result = $model->field(array("act"=>true, "group"=>true, "_id"=>true))->where($map)->find();
    	print_r($result);
    	print_r($model->_sql());
    	echo PHP_EOL;

    	//$not查询
    	$map = array();
    	$map['day'] = '20140710';
    	$map['_logic'] = 'or';
    	$map['act'] = array('$exists'=>true, '$not'=>array('$in'=>array("click", "listview")) );
    	$map['_complex'] = array('_logic'=>"nor", array("act"=>"down"), array("group"=>"download"));
    	$result = $model->field(array("act"=>true, "group"=>true, "_id"=>true))->where($map)->find();
    	print_r($result);
    	print_r($model->_sql());
    	echo PHP_EOL;
    }
}

MongoDB的查询语法及常见缺陷

与(My)SQL语法对比
http://docs.mongodb.org/manual/reference/sql-comparison/

查询语法:
1 ) . 大于,小于,大于或等于,小于或等于

$gt:大于
$lt:小于
$gte:大于或等于
$lte:小于或等于

例子:

db.collection.find({ "field" : { $gt: value } } ); // greater than : field > value
db.collection.find({ "field" : { $lt: value } } ); // less than : field < value db.collection.find({ "field" : { $gte: value } } ); // greater than or equal to : field >= value
db.collection.find({ "field" : { $lte: value } } ); // less than or equal to : field < = value 如查询j大于3,小于4: db.things.find({j : {$lt: 3}}); db.things.find({j : {$gte: 4}}); 也可以合并在一条语句内: db.collection.find({ "field" : { $gt: value1, $lt: value2 } } ); // value1 < field < value 2) 不等于 $ne 例子: db.things.find( { x : { $ne : 3 } } ); 3) in 和 not in ($in $nin) 语法: db.collection.find( { "field" : { $in : array } } ); 例子: db.things.find({j:{$in: [2,4,6]}}); db.things.find({j:{$nin: [2,4,6]}}); 4) 取模运算$mod 如下面的运算: db.things.find( "this.a % 10 == 1") 可用$mod代替: db.things.find( { a : { $mod : [ 10 , 1 ] } } ) 5) $all $all和$in类似,但是他需要匹配条件内所有的值: 如有一个对象: { a: [ 1, 2, 3 ] } 下面这个条件是可以匹配的: db.things.find( { a: { $all: [ 2, 3 ] } } ); 但是下面这个条件就不行了: db.things.find( { a: { $all: [ 2, 3, 4 ] } } ); 6) $size $size是匹配数组内的元素数量的,如有一个对象:{a:["foo"]},他只有一个元素: 下面的语句就可以匹配:db.things.find( { a : { $size: 1 } } ); 官网上说不能用来匹配一个范围内的元素,如果想找$size<5之类的,他们建议创建一个字段来保存元素的数量。 You cannot use $size to find a range of sizes (for example: arrays with more than 1 element). If you need to query for a range, create an extra size field that you increment when you add elements. 7)$exists $exists用来判断一个元素是否存在: 如: db.things.find( { a : { $exists : true } } ); // 如果存在元素a,就返回 db.things.find( { a : { $exists : false } } ); // 如果不存在元素a,就返回 8) $type $type 基于 bson type来匹配一个元素的类型,像是按照类型ID来匹配,不过我没找到bson类型和id对照表。 db.things.find( { a : { $type : 2 } } ); // matches if a is a string db.things.find( { a : { $type : 16 } } ); // matches if a is an int 9)正则表达式 mongo支持正则表达式,如: db.customers.find( { name : /acme.*corp/i } ); // 后面的i的意思是区分大小写 10) 查询数据内的值 下面的查询是查询colors内red的记录,如果colors元素是一个数据,数据库将遍历这个数组的元素来查询。db.things.find( { colors : "red" } ); 11) $elemMatch 如果对象有一个元素是数组,那么$elemMatch可以匹配内数组内的元素: > t.find( { x : { $elemMatch : { a : 1, b : { $gt : 1 } } } } )
{ "_id" : ObjectId("4b5783300334000000000aa9"),
"x" : [ { "a" : 1, "b" : 3 }, 7, { "b" : 99 }, { "a" : 11 } ]
}$elemMatch : { a : 1, b : { $gt : 1 } } 所有的条件都要匹配上才行。
注意,上面的语句和下面是不一样的。
> t.find( { "x.a" : 1, "x.b" : { $gt : 1 } } )
$elemMatch是匹配{ "a" : 1, "b" : 3 },而后面一句是匹配{ "b" : 99 }, { "a" : 11 }

12) 查询嵌入对象的值

db.postings.find( { "author.name" : "joe" } );
注意用法是author.name,用一个点就行了。更详细的可以看这个链接: dot notation

举个例子:
> db.blog.save({ title : "My First Post", author: {name : "Jane", id : 1}})
如果我们要查询 authors name 是Jane的, 我们可以这样:
> db.blog.findOne({"author.name" : "Jane"})
如果不用点,那就需要用下面这句才能匹配:
db.blog.findOne({"author" : {"name" : "Jane", "id" : 1}})
下面这句:
db.blog.findOne({"author" : {"name" : "Jane"}})
是不能匹配的,因为mongodb对于子对象,他是精确匹配。
13) 元操作符 $not 取反
如:
db.customers.find( { name : { $not : /acme.*corp/i } } );db.things.find( { a : { $not : { $mod : [ 10 , 1 ] } } } ); mongodb还有很多函数可以用,如排序,统计等,请参考原文。

12) or及where等语法
db.blog.findOne({$where : "this.appsname.length > 10"});
db.blog.findOne({$or : [{"category":"apps"}, {"type":"apps"}]});

常见缺陷:
1. 哈希对象中key的顺序

比如,你要存储一个简单的文字对象::
> db.books.insert({ title: "Woe from Wit", meta: { author: "A. Griboyedov", year: 1823 } });
太棒了!现在我们有了一条书籍记录。再比如,以后我们会想找所有1823年出版的作者是 A. Griboyedov 的书。这里不太可能返回多个结果,但至少应该有《 Woe from Wit 》这本书,因为我们刚刚插入了这条记录,对不对?
> db.books.find({ meta: { year: 1823, author: "A. Griboyedov" } });

< No results returned 发生了什么?我们不是刚刚插入了这本书的数据吗?让我们尝试调换key的顺序: > db.books.find({ meta: { author: "A. Griboyedov", year: 1823 } });

< { _id: ..., title: "Woe from Wit", meta: { ... } } 陷阱: 在MongoDB中key的顺序非常重要,{ a: 1, b: 2 } 和 { b: 2, a: 1 }是不匹配的。 为什么: MongoDB使用叫做BSON的二进制数据格式。在BSON中key的顺序非常重要。 注意,JSON对象是一个无序的键/值对集合。 2. undefined, null and undefined 想必很多人都还记得那个undefined, null 的关系、特性很混乱的时候吧!在JavaScript的世界中undefined、null代表着两个不同的值,严格来说它们是不一样的:undefined!== NULL。当然,在非严格的情况下他们确实相等:undefined == null。有些人很小心的使用它们,而另一部分人将两者随意交替使用。说到底我们的问题是:JavaScript确实存在两个不同但很相似的值。 > db.test.insert({"id":0, "name":null, "data":undefined});
> db.test.findOne();
{
"_id" : ObjectId("535b602b4c37cac2728bbc71"),
"id" : 0,
"name" : null,
"data" : null
}
> db.test.insert({"id":1, "data":null});
> db.test.find({"name":null});
{ "_id" : ObjectId("535b61a54c37cac2728bbc7b"), "id" : 0, "name" : null, "data" : null }
{ "_id" : ObjectId("535b61a94c37cac2728bbc7c"), "id" : 1, "data" : null }

3. Soft limits, hard limits and no limits
> db.test.find({"name":null}).limit(0);
{ "_id" : ObjectId("535b61a54c37cac2728bbc7b"), "id" : 0, "name" : null, "data" : null }
{ "_id" : ObjectId("535b61a94c37cac2728bbc7c"), "id" : 1, "data" : null }
> db.test.find({"name":null}).limit(-1);
{ "_id" : ObjectId("535b61a54c37cac2728bbc7b"), "id" : 0, "name" : null, "data" : null }
> db.test.find({"name":null}).limit(-2);
{ "_id" : ObjectId("535b61a54c37cac2728bbc7b"), "id" : 0, "name" : null, "data" : null }
{ "_id" : ObjectId("535b61a94c37cac2728bbc7c"), "id" : 1, "data" : null }
> db.test.find({"name":null}).limit(-3);
{ "_id" : ObjectId("535b61a54c37cac2728bbc7b"), "id" : 0, "name" : null, "data" : null }
{ "_id" : ObjectId("535b61a94c37cac2728bbc7c"), "id" : 1, "data" : null }

请注意MongoDB的limit中,0,负数。
对MongoDB来说,skip的大小严重影响性能,应该严格避免特别大的skip操作。

4. MongoDB中的数组
陷阱: 尽可能的避免数组或者嵌套数组以及其他一对多关系的数据存在于文档之中,并且在需要查询的时候,通常我们倾向于按照一对一关系去查询。然而对于使用数字键(例如{ 'a.0.x': Y }意味着字段a的第一个元素的x字段必须为Y)的混合型文档很可能会让人感觉非常别扭,当然这也取决于数据的复杂程度。

5. 查询长度条件
目前没有直接查询内容长度的语法,如:
db.books.find({"books" : {$length : {$gt : 1}}});

目前只能使用以下方法:
db.books.find({"books" : {$exists: true}, $where : " (7 <= this.length) && (this.length <= 14) "}); `$where queries are not very efficient` 但是这方法,效率缓期低下,没有索引的查询,速度真的很慢。:( 不过,我发现在加了索引的字段上,使用正则进行查询,效率还不错,至少命中了索引,效率提升了10倍。 [js] > db.apps.find({$where:"(this.id.length>6) && (this.id.length<15)"}).count(); 2548 > db.apps.find({id:/\w{7,16}/i}).count(); 2548 > db.apps.find({$where:"(this.id.length>6) && (this.id.length<15)"}).explain(); { "cursor" : "BasicCursor", "isMultiKey" : false, "n" : 2548, "nscannedObjects" : 88736, "nscanned" : 88736, "nscannedObjectsAllPlans" : 88736, "nscannedAllPlans" : 88736, "scanAndOrder" : false, "indexOnly" : false, "nYields" : 1, "nChunkSkips" : 0, "millis" : 1523, "indexBounds" : { }, "server" : "shuhaimac.local:27017" } > db.apps.find({id:/\w{7,16}/i}).explain(); { "cursor" : "BtreeCursor id_1 multi", "isMultiKey" : false, "n" : 2548, "nscannedObjects" : 2548, "nscanned" : 88736, "nscannedObjectsAllPlans" : 2548, "nscannedAllPlans" : 88736, "scanAndOrder" : false, "indexOnly" : false, "nYields" : 0, "nChunkSkips" : 0, "millis" : 122, "indexBounds" : { "id" : [ [ "", { } ], [ /\w{7,16}/i, /\w{7,16}/i ] ] }, "server" : "shuhaimac.local:27017" } [/js]

MongoDB的备份(mongodump)与恢复(mongorestore)

一次MongoDB的备份(mongodump)与恢复(mongorestore)操作实例,从a机备份一份数据到b机进行调试。

$mongodump -h 192.168.1.2 -d apps -o apps
$zip -rq apps.zip apps
$scp 192.168.1.2:~/apps.zip .
$unzip apps.zip apps
$mongorestore -h 192.168.1.3 -d apps --directoryperdb ./apps

----
--------------------------------------------------------------------
----

$mongodump -h dbhost -d dbname -o dbdirectory

-h:MongDB所在服务器地址,例如:127.0.0.1,当然也可以指定端口号:127.0.0.1:27017

-d:需要备份的数据库实例,例如:test

-o:备份的数据存放位置,例如:c:\data\dump,当然该目录需要提前建立,在备份完成后,系统自动在dump目录下建立一个test目录,这个目录里面存放该数据库实例的备份数据。

$mongorestore -h dbhost -d dbname --directoryperdb dbdirectory

-h:MongoDB所在服务器地址

-d:需要恢复的数据库实例,例如:test,当然这个名称也可以和备份时候的不一样,比如test2

--directoryperdb:备份数据所在位置,例如:c:\data\dump\test,这里为什么要多加一个test,而不是备份时候的dump,读者自己查看提示吧!

--drop:恢复的时候,先删除当前数据,然后恢复备份的数据。就是说,恢复后,备份后添加修改的数据都会被删除,慎用哦!

MongoDB to CSV/TXT,Mongodb查询内容输出到外部文件

方法一:

Every once in a while, I need to give a non-technical user (like a business analyst) data residing in MongoDB; consequently, I export the target data as a CSV file (which they can presumably slice and dice once they import it into Excel or some similar tool). Mongo has a handy export utility that takes a bevy of options, however, there is an outstanding bug and some general confusion as to how to properly export data in CSV format.

Accordingly, if you need to export some specific data from MongoDB into CSV format, here’s how you do it. The key parameters are connection information to include authentication, an output file, and most important, a list of fields to export. What’s more, you can provide a query in escaped JSON format.

You can find the mongoexport utility in your Mongo installation bin directory. I tend to favor verbose parameter names and explicit connection information (i.e. rather than a URL syntax, I prefer to spell out the host, port, db, etc directly). As I’m targeting specific data, I’m going to specify the collection; what’s more, I’m going to further filter the data via a query.

ObjectId’s can be referenced via the $oid format; furthermore, you’ll need to escape all JSON quotes. For example, if my query is against a users collection and filtered by account_id (which is an ObjectId), the query via the mongo shell would be:

Mongo Shell Query

db.users.find({account_id:ObjectId('5058ca07b7628c0002099006')})

Via the command line à la monogexport, this translates to:

Collections and queries

 --collection users --query "{\"account_id\": {\"\$oid\": \"5058ca07b7628c0002000006\"}}"

Finally, if you want to only export a portion of the fields in a user document, for example, name, email, and created_at, you need to provide them via the fields parameter like so:

Fields declaration

--fields name,email,created_at
Putting it all together yields the following command:

Puttin’ it all together

mongoexport --host mgo.acme.com --port 10332 --username acmeman --password 12345  \
--collection users --csv --fields name,email,created_at --out all_users.csv --db my_db \
--query "{\"account_id\": {\"\$oid\": \"5058ca07b7628c0999000006\"}}"

Of course, you can throw this into a bash script and parameterize the collection, fields, output file, and query with bash’s handy $1, $2, etc variables.

方法二:

mongo test --eval "printjson(db.getCollectionNames())"

方法三:
vim myjsfile.js
cursor = db.apps.find({'id':{$gt:"0"}},{'_id':0, 'id':1, 'name':1});
while ( cursor.hasNext() ) {
var row = cursor.next();
print( row['id'] + "\t" + row['name'] );
}

mongo localhost:27017/test myjsfile.js >> output.csv