八、实际应用一些开发人员可能害怕使用一种新型的数据库,因为它和他们以前工作中用过的那些不同。在理论上学习新事物不同于在实践中学习如何使用,所以,这部分内容将通过比较基于SQL的关系型数据库,比如MySQL,来解释如何用MongoDB来开发实际应用,这样
八、实际应用
一些开发人员可能害怕使用一种新型的数据库,因为它和他们以前工作中用过的那些不同。
在理论上学习新事物不同于在实践中学习如何使用,所以,这部分内容将通过比较基于SQL的关系型数据库,比如MySQL,来解释如何用MongoDB来开发实际应用,这样就可以熟悉这两种途径
的不同。
例如,我们将构建一个blog系统,有用户,提交和评论功能。在使用关系型数据库的时候,你可以象下面这样通过定义表模式来实现它。
在MongoDB中实现同样的文档定义如下:
$users = array(
'username' => 'crodas',
'name' => 'Cesar Rodas',
);
$posts = array(
'uri' => '/foo-bar-post',
'author_id' => $users->_id,
'title' => 'Foo bar post',
'summary' => 'This is a summary text',
'body' => 'This is the body',
'comments' => array(
array(
'name' => 'user',
'email' => 'foo@bar.com',
'content' => 'nice post'
)
)
);
你可能注意到,我们只用一个文档就代替了posts和comments两个表,这是因为comments是post文档的子文档。这样做使实现更简单,在你想存取发布内容和它的评论时,会节省查询数据库的时间。
为了更简洁,用户所做评论的细节可以和评论的定义合并,所以你可以用一个查询来获取所发布的内容,评论和用户这些信息。
$posts = array(
'uri' => '/foo-bar-post',
'author_id' => $users->_id,
'author_name' => 'Cesar Rodas',
'author_username' => 'crodas',
'title' => 'Foo bar post',
'summary' => 'This is a summary text',
'body' => 'This is the body',
'comments' => array(
array(
'name' => 'user',
'email' => 'foo@bar.com',
'comment' => 'nice post'
),
)
);
这意味着会存在一些重复信息,但现在磁盘空间比CPU的RAM要便宜得多,以空间换时间,网站访问者的耐心和时间更重要。如果你关注重复信息的同步,那么在更新author信息的时候,你可以执行下面这个更新查询来解决这个问题。
$filter = array(
'author_id' => $author['_id'],
);
$data = array(
'$set' => array(
'author_name' => 'Cesar D. Rodas',
'author_username' => 'cesar',
)
);
$collection->update($filter, $data, array(
'multiple' => true)
);
以上是我们对数据模型的转换和优化,下面将重写一些用在MongoDB中的和SQL等价的查询。
SELECT * FROM posts
INNER JOIN users ON users.id = posts.user_id
WHERE URL = :url;
SELECT * FROM comments WHERE post_id = $post_id;
首先,增加索引:
$collection->ensureIndex(
array('uri' => 1),
array('unique' => 1, 'background')
);
$collection->find(array('uri' => ''));
INSERT INTO comments(post_id, name, email, contents)
VALUES(:post_id, :name, :email, :comment);
$comment = array(
'name' => $_POST['name'],
'email' => $_POST['email'],
'comment' => $_POST['comment'],
);
$filter = array(
'uri' => $_POST['uri'],
);
$collection->update($filter, array(
'$push' => array('comments' => $comment))
);
SELECT * FROM posts WHERE id IN (
SELECT DISTINCT post_id FROM comments WHERE email =
:email
);
首先,增加索引:
$collection->ensureIndex(
array('comments.email' => 1),
array('background' => 1)
);
$collection->find( array('comments.email' => $email)
);
九、用MongoDB存储文件
MongoDB也提供许多超过基本数据库操作的特点。例如,它提供了在数据库中存储小文件和大文件的解决方案。
文件被自动分块(块)。如果MongoDB运行在自动分片(auto-sharded)环境,文件块也会被跨多个服务器复制。
有效地解决文件的存储是一个相当困难的问题,尤其是当你需要管理大量的文件时。把文件保存在本地文件系统中通常不是个好的方案。
一个困难的例子是YouTube必须有效地服务那些上百万视频的小图片,或者由Facebook为数十亿图片提供的高效运行服务。
MongoDB通过创建两个内部集合(collections)来解决这个问题:文件集合保存关于文件元数据的信息,块集合保存关于文件块的信息。
如果你想存储一个大的视频文件,你可以使用如下这样的代码:
$metadata = array(
"filename" => "path.avi",
"downloads" => 0,
"comment" => "This file is foo bar",
"permissions" => array(
"crodas" => "write",
"everybody" => "read",
)
);
$grid = $db->getGridFS();
$grid->storeFile("/file/to/path.avi", $metadata);
正如你所看到的,这很简单且容易理解。
十、Map-Reduce
Map-Reduce是一种处理大量信息的操作手段。map操作应用于每个文档并产生一套新的key-value数据对。reduce操作使用map功能产生的结果并产生基本每个key的单一结果。
MongoDB Map-Reduce功能可以应用到集合上用于数据转换,这和Hadoop很类似。
当map处理完成后,结果被保存并且通过键值(key
value)被分组。对每个结果键(key),使用2个参数来调用reduce功能:键(key)及其所有值的数组。
为了更好地理解它的工作原理,我们假设有了前面定义过的blog的提交文档,接下来你想使每个提交的内容有一系列的tag,如果你想获得关于这些tag的统计情况,你只需要像下面这样计算一下即可:
首先定义map和reduce功能代码,
$map = new MongoCode("function () {
var i;
for (i=0; i
emit(this.tags[i], {count: 1});
}
}");
$reduce = new MongoCode("function (key, values) {
var i, total=0;
for (i=0; i
total = values[i].count;
}
return {count: total}
}");
然后执行map-reduce命令:
$map_reduce = array(
'out' => 'tags_info',
'verbose' => true,
'mapreduce' => 'posts',
'map' => $map,
'reduce' => $reduce,
);
$information = $db->command($map_reduce);
var_dump($information);
如果MongoDB运行在切片(sharded)环境,那么这个数据处理功能将会在所有shard节点上并行。
要知道执行map-reduce处理通常是很慢的。它的目的是把大量的数据分布在许多服务器上。所以,如果你有许多服务器,你就可以把这个操作分布在这些服务器上进行处理并获得结果,这会比在一台服务器上运行所需的时间要少得多。
建议在后台运行map-reduce处理,因为它们需要花比较长的时候才能完成。在这种情况下,如果通过Gearman来异步管理启动它是个不错的方法。
十一、Auto-sharding
在前面多次提到sharding,但你可能并不熟悉这个概念。
Data sharding是一种把数据分布在多个服务器上的数据库技术手段。
MongoDB只需要很少的配置就可完成auto-sharding。然而,安装和配置一个shard已超出本文章的范围。
下面这张图展示了工作在shard环境中的MongoDB,这样你会在你使用sharding时都发生了什么有个了解。
十二、其它
正则表达式使用面面的格式:
"//"
和SQL语句中的 username LIKE '%bar%'等价的方式如下:
$filter = array(
'username' => new MongoRegex("/.*bar.*/i"),
);
$collection->find($filter);
?>
在使用Regex时要小心,大多时候它不能使用索引,因此它将对整个数据扫描,所以比较好的方法是对文档的数目进行限制。
$filter = array(
'username' => new MongoRegex("/.*bar.*/i"),
'karma' => array('$gt' => 10),
);
$collection->find($filter);
?>
使用Regex可以完成如下这个复杂的查询:
$filter = array(
'username' => new
MongoRegex("/[a-z][a-z0-9\_]+(\_[0-9])?/i"),
'karma' => array('$gt' => 10),
);
$collection->find($filter);
?>