1. 程式人生 > >用PHP開發基於MongoDB的php應用

用PHP開發基於MongoDB的php應用

一、連線資料庫主機 連線本地主機,埠為27017: $connection = new Mongo(); 連線遠端主機,埠為預設埠: $connection= new Mongo( "192.168.2.1" ); 連線遠端主機,埠為指定埠: $connection = new Mongo( "192.168.2.1:65432" ); 選擇資料庫,如果指定的資料庫不存在,則會自動建立一個新的資料庫,有2種方法: $db = $connection->selectDB('dbname'); 或 $db = $connection->dbname; 選擇集合(collection),與使用關係型資料庫中的表類似,有2種方法: $collection = $db->selectCollection('people'); 或 $collection = $db->people; 二、插入新文件(document)
collection物件用來執行資訊管理,例如,想儲存一個關於某人的資訊,可以如下編碼: $person = array(  'name' => 'Cesar Rodas',  'email' => '[email protected]',  '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引數用於等待MongoDB完成操作,以便確定是否成功,預設值為false,當有大量記錄插入時使用該引數會比較有用。 插入新文件後,MongoDB會返回一個記錄標識。 三、更新文件
例如,更新上面已經建立的個人資訊,增加sessions屬性值,在第1個address處增加address2屬性,刪除第2個address,程式碼如下: 首先,定義一個filter(過濾器)告訴MongoDB要更新一個指定的文件 $filter = array('email' => '[email protected]'); $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也支援批量更新,與關係型資料庫類似,可以更新給定條件的所有文件,如果想這麼做的話,就需要設定options的multiple的值為true. 四、查詢文件
定義一個符合給定標準的條件過濾器,通過使用查詢選擇器來獲取文件。 例,通過e-mail address來獲取資訊: $filter = array('email' => '[email protected]'); $cursor = $collection->find($filter); foreach ($cursor as $user)  {   var_dump($user); } 例,獲取sessions大於10的資訊: $filter = array('sessions' => array('$gt' => 10)); $cursor = $collection->find($filter); 例,獲取沒有設定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); 有一個重要的細節需要注意,只有當需要結果的時候查詢才會被執行,在第1個例子中,當foreach迴圈開始時,查詢才被執行。 這是個很有用的特性,因為這可以通過在遊標(cursor)中增加選項來取回結果,恰好在定義查詢後,執行查詢前這個時刻。例如,可以設定選項來執行分頁,或者獲取指定數目的匹配的文件。 $total = $cursor->total(); $cursor->limit(20)->skip(40); foreach($cursor as $user) { } 五、獲取文件的聚類 MongoDB支援結果的聚類,類似於關係資料庫,可以使用count,distinct和group等聚類操作。 聚類查詢返回陣列(array),而不是整個文件物件。 分組操作允許定義用Javascript編寫的MongoDB伺服器端功能,該操作執行分組屬性。因為可以執行許多帶有分組值的操作型別,所以會更靈活,但是相比SQL執行例如SUM(),AVG()等 簡單的分組操作來說,這還是有些困難。 下面這個例子演示瞭如何獲取國家的的地址列表,以及匹配地址的國家出現的次數: $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); 要注意,預設所有符合條件的文件都會被刪除,如果只想刪除符合條件的第1個文件,那麼在給remove函式的第二個引數賦值為true。 七、索引支援 有一個很重要的特點,使得決定選擇MongoDB,而不是選擇其它的類似的面向文件的資料庫,這個特點就是對索引的支援,這和關係型資料庫的表索引很類似,並不是所有的面向文件的資料庫都 提供內建的索引支援。 使用MongoDB的建立索引功能可以避免在查詢期間在所有文件中進行操作,這就象關係資料庫中使用索引來避免全表查詢一樣。這可以加速那些涉及到索引屬性的符合條件的文件的查詢。 例如,如果想在e-mail地址屬性上建立唯一的索引,如下所示: $collection->ensureIndex(  array('email' => 1),  array('unique' => true, 'background' => true) ); 第1個數組引數描述將要成為索引的所有屬性,可以是1個或多個屬性。 預設情況下,索引的建立是一個同步操作,所以,如果文件數目很大的話,把索引的建立放在後臺執行會是個好主意,就象上面例子所演示的。 只有一個屬性的索引可能不夠用,下面這個例子演示如何通過在2個屬性上定義索引來加速查詢。 $collection->ensureIndex(     array('address.country' => 1, 'sessions' => 1),   array('background' => true) ); 每個索引的值定義了索引的順序:1表示降序(descending),-1表示升序(ascending) 索引順序在有排序選項的查詢優化中是有用的,如下例所示: $filter = array(      'address.country' => 'PY', ); $cursor = $collection->find($filter)->order(   array('sessions' => -1) ); $collection->ensureIndex(  array('address.country' => 1, 'sessions' => -1),  array('background' => true) ); 八、實際應用 一些開發人員可能害怕使用一種新型的資料庫,因為它和他們以前工作中用過的那些不同。 在理論上學習新事物不同於在實踐中學習如何使用,所以,這部分內容將通過比較基於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' => '[email protected]',    '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' => '[email protected]',    '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' => '<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 < 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} }"); 然後執行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時都發生了什麼有個瞭解。 \

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