一、連接數(shù)據(jù)庫(kù)主機(jī)
連接本地主機(jī),端口為27017:
$connection = new Mongo();
連接遠(yuǎn)程主機(jī),端口為默認(rèn)端口:
$connection= new Mongo( "192.168.2.1" );
連接遠(yuǎn)程主機(jī),端口為指定端口:
$connection = new Mongo( "192.168.2.1:65432" );
選擇數(shù)據(jù)庫(kù),如果指定的數(shù)據(jù)庫(kù)不存在,則會(huì)自動(dòng)創(chuàng)建一個(gè)新的數(shù)據(jù)庫(kù),有2種方法:
$db = $connection->selectDB('dbname');
或
$db = $connection->dbname;
選擇集合(collection),與使用關(guān)系型數(shù)據(jù)庫(kù)中的表類似,有2種方法:
$collection = $db->selectCollection('people');
或
$collection = $db->people;
二、插入新文檔(document)
collection對(duì)象用來執(zhí)行信息管理,例如,想存儲(chǔ)一個(gè)關(guān)于某人的信息,可以如下編碼:
$person = array(
'name' => 'Cesar Rodas',
'email' => 'crodas@php.net',
'address' => array(
array(
'country' => 'PY',
'zip' => '2160',
'address1' => 'foo bar'
),
array(
'country' => 'PY',
'zip' => '2161',
'address1' => 'foo bar bar foo'
),
),
'sessions' => 0,
);
$safe_insert = true;
$collection->insert($person, $safe_insert);
$person_identifier = $person['_id'];
其中:
$safe_insert參數(shù)用于等待MongoDB完成操作,以便確定是否成功,默認(rèn)值為false,當(dāng)有大量記錄插入時(shí)使用該參數(shù)會(huì)比較有用。
插入新文檔后,MongoDB會(huì)返回一個(gè)記錄標(biāo)識(shí)。
三、更新文檔
例如,更新上面已經(jīng)創(chuàng)建的個(gè)人信息,增加sessions屬性值,在第1個(gè)address處增加address2屬性,刪除第2個(gè)address,代碼如下:
首先,定義一個(gè)filter(過濾器)告訴MongoDB要更新一個(gè)指定的文檔
$filter = array('email' => 'crodas@php.net');
$new_document = array(
'$inc' => array('sessions' => 1),
'$set' => array(
'address.0.address2' => 'Some foobar street',
),
'$unset' => array('address.1' => 1),
);
$options['multiple'] = false;
$collection->update(
$filter,
$new_document,
$options
);
MongoDB也支持批量更新,與關(guān)系型數(shù)據(jù)庫(kù)類似,可以更新給定條件的所有文檔,如果想這么做的話,就需要設(shè)置options的multiple的值為true.
四、查詢文檔
定義一個(gè)符合給定標(biāo)準(zhǔn)的條件過濾器,通過使用查詢選擇器來獲取文檔。
例,通過e-mail address來獲取信息:
$filter = array('email' => 'crodas@php.net');
$cursor = $collection->find($filter);
foreach ($cursor as $user)
{
var_dump($user);
}
例,獲取sessions大于10的信息:
$filter = array('sessions' => array('$gt' => 10));
$cursor = $collection->find($filter);
例,獲取沒有設(shè)置sessions屬性的信息:
$filter = array(
'sessions' => array('$exists' => false)
);
$cursor = $collection->find($filter);
例,獲取地址在PY并且sessions大于15的信息:
$filter = array(
'address.country' => 'PY',
'sessions' => array('$gt' => 10)
);
$cursor = $collection->find($filter);
有一個(gè)重要的細(xì)節(jié)需要注意,只有當(dāng)需要結(jié)果的時(shí)候查詢才會(huì)被執(zhí)行,在第1個(gè)例子中,當(dāng)foreach循環(huán)開始時(shí),查詢才被執(zhí)行。
這是個(gè)很有用的特性,因?yàn)檫@可以通過在游標(biāo)(cursor)中增加選項(xiàng)來取回結(jié)果,恰好在定義查詢后,執(zhí)行查詢前這個(gè)時(shí)刻。例如,可以設(shè)置選項(xiàng)來執(zhí)行分頁(yè),或者獲取指定數(shù)目的匹配的文檔。
$total = $cursor->total();
$cursor->limit(20)->skip(40);
foreach($cursor as $user) {
}
五、獲取文檔的聚類
MongoDB支持結(jié)果的聚類,類似于關(guān)系數(shù)據(jù)庫(kù),可以使用count,distinct和group等聚類操作。
聚類查詢返回?cái)?shù)組(array),而不是整個(gè)文檔對(duì)象。
分組操作允許定義用Javascript編寫的MongoDB服務(wù)器端功能,該操作執(zhí)行分組屬性。因?yàn)榭梢詧?zhí)行許多帶有分組值的操作類型,所以會(huì)更靈活,但是相比SQL執(zhí)行例如SUM(),AVG()等
簡(jiǎn)單的分組操作來說,這還是有些困難。
下面這個(gè)例子演示了如何獲取國(guó)家的的地址列表,以及匹配地址的國(guó)家出現(xiàn)的次數(shù):
$countries = $collection->distinct(
array("address.country")
);
$result = $collection->group(
array("address.country" => True),
array("sum" => 0),
"function (obj, prev) { prev.sum += 1; }",
array("session" => array('$gt' => 10))
);
六、刪除文檔
刪除文檔與獲取或更新文檔很類似。
$filter = array('field' => 'foo');
$collection->remove($filter);
要注意,默認(rèn)所有符合條件的文檔都會(huì)被刪除,如果只想刪除符合條件的第1個(gè)文檔,那么在給remove函數(shù)的第二個(gè)參數(shù)賦值為true。
七、索引支持
有一個(gè)很重要的特點(diǎn),使得決定選擇MongoDB,而不是選擇其它的類似的面向文檔的數(shù)據(jù)庫(kù),這個(gè)特點(diǎn)就是對(duì)索引的支持,這和關(guān)系型數(shù)據(jù)庫(kù)的表索引很類似,并不是所有的面向文檔的數(shù)據(jù)庫(kù)都
提供內(nèi)置的索引支持。
使用MongoDB的創(chuàng)建索引功能可以避免在查詢期間在所有文檔中進(jìn)行操作,這就象關(guān)系數(shù)據(jù)庫(kù)中使用索引來避免全表查詢一樣。這可以加速那些涉及到索引屬性的符合條件的文檔的查詢。
例如,如果想在e-mail地址屬性上建立唯一的索引,如下所示:
$collection->ensureIndex(
array('email' => 1),
array('unique' => true, 'background' => true)
);
第1個(gè)數(shù)組參數(shù)描述將要成為索引的所有屬性,可以是1個(gè)或多個(gè)屬性。
默認(rèn)情況下,索引的創(chuàng)建是一個(gè)同步操作,所以,如果文檔數(shù)目很大的話,把索引的創(chuàng)建放在后臺(tái)運(yùn)行會(huì)是個(gè)好主意,就象上面例子所演示的。
只有一個(gè)屬性的索引可能不夠用,下面這個(gè)例子演示如何通過在2個(gè)屬性上定義索引來加速查詢。
$collection->ensureIndex(
array('address.country' => 1, 'sessions' => 1),
array('background' => true)
);
每個(gè)索引的值定義了索引的順序:1表示降序(descending),-1表示升序(ascending)
索引順序在有排序選項(xiàng)的查詢優(yōu)化中是有用的,如下例所示:
$filter = array(
'address.country' => 'PY',
);
$cursor = $collection->find($filter)->order(
array('sessions' => -1)
);
$collection->ensureIndex(
array('address.country' => 1, 'sessions' => -1),
array('background' => true)
);
八、實(shí)際應(yīng)用
一些開發(fā)人員可能害怕使用一種新型的數(shù)據(jù)庫(kù),因?yàn)樗退麄円郧肮ぷ髦杏眠^的那些不同。
在理論上學(xué)習(xí)新事物不同于在實(shí)踐中學(xué)習(xí)如何使用,所以,這部分內(nèi)容將通過比較基于SQL的關(guān)系型數(shù)據(jù)庫(kù),比如MySQL,來解釋如何用MongoDB來開發(fā)實(shí)際應(yīng)用,這樣就可以熟悉這兩種途徑
的不同。
例如,我們將構(gòu)建一個(gè)blog系統(tǒng),有用戶,提交和評(píng)論功能。在使用關(guān)系型數(shù)據(jù)庫(kù)的時(shí)候,你可以象下面這樣通過定義表模式來實(shí)現(xiàn)它。

在MongoDB中實(shí)現(xiàn)同樣的文檔定義如下:
$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'
)
)
);
你可能注意到,我們只用一個(gè)文檔就代替了posts和comments兩個(gè)表,這是因?yàn)閏omments是post文檔的子文檔。這樣做使實(shí)現(xiàn)更簡(jiǎn)單,在你想存取發(fā)布內(nèi)容和它的評(píng)論時(shí),會(huì)節(jié)省查詢數(shù)據(jù)庫(kù)的時(shí)間。
為了更簡(jiǎn)潔,用戶所做評(píng)論的細(xì)節(jié)可以和評(píng)論的定義合并,所以你可以用一個(gè)查詢來獲取所發(fā)布的內(nèi)容,評(píng)論和用戶這些信息。
$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'
),
)
);
這意味著會(huì)存在一些重復(fù)信息,但現(xiàn)在磁盤空間比CPU的RAM要便宜得多,以空間換時(shí)間,網(wǎng)站訪問者的耐心和時(shí)間更重要。如果你關(guān)注重復(fù)信息的同步,那么在更新author信息的時(shí)候,你可以執(zhí)行下面這個(gè)更新查詢來解決這個(gè)問題。
$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)
);
以上是我們對(duì)數(shù)據(jù)模型的轉(zhuǎn)換和優(yōu)化,下面將重寫一些用在MongoDB中的和SQL等價(jià)的查詢。
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' => '<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存儲(chǔ)文件
MongoDB也提供許多超過基本數(shù)據(jù)庫(kù)操作的特點(diǎn)。例如,它提供了在數(shù)據(jù)庫(kù)中存儲(chǔ)小文件和大文件的解決方案。
文件被自動(dòng)分塊(塊)。如果MongoDB運(yùn)行在自動(dòng)分片(auto-sharded)環(huán)境,文件塊也會(huì)被跨多個(gè)服務(wù)器復(fù)制。
有效地解決文件的存儲(chǔ)是一個(gè)相當(dāng)困難的問題,尤其是當(dāng)你需要管理大量的文件時(shí)。把文件保存在本地文件系統(tǒng)中通常不是個(gè)好的方案。
一個(gè)困難的例子是YouTube必須有效地服務(wù)那些上百萬視頻的小圖片,或者由Facebook為數(shù)十億圖片提供的高效運(yùn)行服務(wù)。
MongoDB通過創(chuàng)建兩個(gè)內(nèi)部集合(collections)來解決這個(gè)問題:文件集合保存關(guān)于文件元數(shù)據(jù)的信息,塊集合保存關(guān)于文件塊的信息。
如果你想存儲(chǔ)一個(gè)大的視頻文件,你可以使用如下這樣的代碼:
$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);
正如你所看到的,這很簡(jiǎn)單且容易理解。
十、Map-Reduce
Map-Reduce是一種處理大量信息的操作手段。map操作應(yīng)用于每個(gè)文檔并產(chǎn)生一套新的key-value數(shù)據(jù)對(duì)。reduce操作使用map功能產(chǎn)生的結(jié)果并產(chǎn)生基本每個(gè)key的單一結(jié)果。
MongoDB Map-Reduce功能可以應(yīng)用到集合上用于數(shù)據(jù)轉(zhuǎn)換,這和Hadoop很類似。
當(dāng)map處理完成后,結(jié)果被保存并且通過鍵值(key value)被分組。對(duì)每個(gè)結(jié)果鍵(key),使用2個(gè)參數(shù)來調(diào)用reduce功能:鍵(key)及其所有值的數(shù)組。
為了更好地理解它的工作原理,我們假設(shè)有了前面定義過的blog的提交文檔,接下來你想使每個(gè)提交的內(nèi)容有一系列的tag,如果你想獲得關(guān)于這些tag的統(tǒng)計(jì)情況,你只需要像下面這樣計(jì)算一下即可:
首先定義map和reduce功能代碼,
$map = new MongoCode("function () {
var i;
for (i=0; i < this.tags.length; i++) {
emit(this.tags[i], {count: 1});
}
}");
$reduce = new MongoCode("function (key, values) {
var i, total=0;
for (i=0; i < values.length; i++) {
total = values[i].count;
}
return {count: total}
}");
然后執(zhí)行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運(yùn)行在切片(sharded)環(huán)境,那么這個(gè)數(shù)據(jù)處理功能將會(huì)在所有shard節(jié)點(diǎn)上并行。
要知道執(zhí)行map-reduce處理通常是很慢的。它的目的是把大量的數(shù)據(jù)分布在許多服務(wù)器上。所以,如果你有許多服務(wù)器,你就可以把這個(gè)操作分布在這些服務(wù)器上進(jìn)行處理并獲得結(jié)果,這會(huì)比在一臺(tái)服務(wù)器上運(yùn)行所需的時(shí)間要少得多。
建議在后臺(tái)運(yùn)行map-reduce處理,因?yàn)樗鼈冃枰ū容^長(zhǎng)的時(shí)候才能完成。在這種情況下,如果通過Gearman來異步管理啟動(dòng)它是個(gè)不錯(cuò)的方法。
十一、Auto-sharding
在前面多次提到sharding,但你可能并不熟悉這個(gè)概念。
Data sharding是一種把數(shù)據(jù)分布在多個(gè)服務(wù)器上的數(shù)據(jù)庫(kù)技術(shù)手段。
MongoDB只需要很少的配置就可完成auto-sharding。然而,安裝和配置一個(gè)shard已超出本文章的范圍。
下面這張圖展示了工作在shard環(huán)境中的MongoDB,這樣你會(huì)在你使用sharding時(shí)都發(fā)生了什么有個(gè)了解。

十二、其它
正則表達(dá)式使用面面的格式:
"/<regex>/<flags>"
和SQL語句中的 username LIKE '%bar%'等價(jià)的方式如下:
<?php
$filter = array(
'username' => new MongoRegex("/.*bar.*/i"),
);
$collection->find($filter);
?>
在使用Regex時(shí)要小心,大多時(shí)候它不能使用索引,因此它將對(duì)整個(gè)數(shù)據(jù)掃描,所以比較好的方法是對(duì)文檔的數(shù)目進(jìn)行限制。
<?php
$filter = array(
'username' => new MongoRegex("/.*bar.*/i"),
'karma' => array('$gt' => 10),
);
$collection->find($filter);
?>
使用Regex可以完成如下這個(gè)復(fù)雜的查詢:
<?php
$filter = array(
'username' => new MongoRegex("/[a-z][a-z0-9\_]+(\_[0-9])?/i"),
'karma' => array('$gt' => 10),
);
$collection->find($filter);