2008年01月20日

[Web] 連結分享

PHP

Zend Framework

JavaScript

jQuery

CSS

Web

Design

RoR

Database

Programming

Software

Other


Posted by jaceju at 1:55回應(1)連結分享

2008年01月16日

[PHP] mysql_query 的記憶體使用與分頁方式

說明

這個實驗主要是探討在 TWPUG 上的這篇 FIEND 寫的: [原創] [分享] 小弟寫的 cakephp 換頁 排序 功能 (第一版)

幾個實驗重點如下:

  • FIEND 提到兩次 Query 不是一個好方式,他的做法是用一次 Query 配合 while + mysql_result 就能做到分頁效果。

  • 另外 shirock 從 PHP 原始碼的部份解釋 PHP 和 MySQL 抓資料後的處理方式,但 FIEND 卻說「用屁股想都知道 PHP 不可能把 QUERY 結果 全部拉回 PHP 端記憶體」。

  • 還有 shirock 提到:「參考文章中已經很明白指出 mysql_query 跳過 PHP 內建記憶體配置機制,而直接使用 mysql C library 的函數儲存資料在 PHP 程序這端。而 memory_limit 只會管制到 PHP 內建記憶體配置機制的使用上限。所以 mysql_query 查詢大量資料時,不會受到 memory_limit 的限制。」

基本上我從來不知道屁股可以用來思考,所以我還是要實事求是,用 FIEND 的方法實驗一次。

註:不過我老是在上廁所時想到一些靈感...Orz

環境

  • Windows XP
  • PHP 5.2.5
  • MySQL 5.0.45
  • memory_limit = 16M (in php.ini)

另外我準備了一個資料庫,裡面包含了四個資料表:

資料表 筆數 硬碟空間
r1000 一千筆 75KB
r10000 一萬筆 743KB
r100000 十萬筆 7,422KB
r1000000 一百萬筆 74,219KB

資料表欄位為一個 id 欄位和一個 value 欄位;而 value 欄位為 varchar(64) ,其內容存的是兩個隨機的 md5 函式結果所組合的字串。可以用以下程式產生:

<?php
echo "CREATE DATABASE `page_test`;\n";
echo "USE `page_test`;\n";

foreach (array(1000, 10000, 100000, 1000000) as $r) {
    $sql = <<<SQL
CREATE TABLE IF NOT EXISTS `r$r` (
  `id` int(10) unsigned NOT NULL auto_increment,
  `value` varchar(64) NOT NULL,
  PRIMARY KEY  (`id`)
) ENGINE=MyISAM;
SQL;
    echo $sql, "\n";
    for ($i = 1; $i <= $r; $i ++) {
        $value = md5(rand(0, 9999)) . md5(rand(0, 9999));
        echo "INSERT INTO `r$r` (`value`) VALUES ('$value');\n";
    }
}

程式

程式部份很簡單,就是按照 FIEND 說的步驟來寫的。只是我這裡改用 CLI 模式執行,以避掉 Apache 的影響。

<?php
echo ini_get('memory_limit'), "\n"; // 16M
echo "\n";
echo "========================\n";
echo "Start.\n";
echo "========================\n";
sleep(10);

$link = mysql_connect('localhost', 'username', 'password');
mysql_select_db('page_test', $link);

$result = mysql_query('SELECT * FROM r1000000', $link);

echo "\n";
echo "========================\n";
echo "mysql_query\n";
echo "========================\n";
sleep(10);

$count  = mysql_num_rows($result);
echo 'count: ', $count, "\n";

echo "\n";
echo "========================\n";
echo "mysql_num_rows\n";
echo "========================\n";
sleep(10);

$start = rand(0, $count - 1);
$end   = $start + 10;
$i     = $start;

echo 'start: ' . $start, "\n";
echo 'end: ' . $end, "\n";

echo "\n";
while ($id = mysql_result($result, $i, 'id')) {
   echo $i, ': ', $id, "\n";
   $i ++;
   if ($i >= $end || $i >= $count) break;
}

echo "\n";
echo "========================\n";
echo "while & mysql_result\n";
echo "========================\n";
sleep(10);

mysql_free_result($result);

echo "\n";
echo "========================\n";
echo "mysql_free_result\n";
echo "========================\n";
sleep(10);

mysql_close($link);

echo "\n";
echo "========================\n";
echo "mysql_close\n";
echo "========================\n";
sleep(10);

而記憶體的觀察我是用 Windows 的工作管理員,然後查看 php.exe 所使用的記憶體大小。在什麼都沒有執行的狀況下, php.exe 會佔用掉約 8MB 的記憶體 (在我的環境下) 。

結果如下 (每項執行 5 次後再取平均值) :

資料表 程式啟動 mysql_query mysql_num_rows while & mysql_result mysql_free_result mysql_close
r1000 8,196KB 8,592KB 8,596KB 8,656KB 8,568KB 8,556KB
r10000 8,196KB 9,496KB 9,500KB 9,560KB 8,592KB 8,580KB
r100000 8,192KB 18,840KB 18,844KB 18,904KB 8,584KB 8,596KB
r1000000 8,192KB 110,684KB 110,688KB 110,748KB 8,796KB 8,784KB

然後我比較 mysql_query 用掉的記憶體和原來資料表使用的硬碟空間:

資料表 mysql_query 硬碟空間
r1000 396KB 75KB
r10000 1,300KB 743KB
r100000 10,648KB 7,422KB
r1000000 102,492KB 74,219KB

很明顯地 mysql_query 所得到的 resource 佔的記憶體空間比 MySQL 使用的硬碟大小還多,這證明了 mysql_query 是有把資料內容抓到 PHP 這邊來;至於為什麼 MySQL 的反而比較小,我想這應該是 MySQL 將資料做壓縮的關係

補充:我上面在 MySQL 壓縮的部份沒有任何根據,應該是錯誤的推論;因此我想就以 normansu 給我的說明為準:

normansu 提到

應該不是.
執行 mysql_store_result 的時候,
在 result 和每一筆 record 都會多一個 Header 的空間,
大小不一定(看 field count).

所以使用的記憶體會比實際 mysql table 大.
在 source 中沒有看到任何 compress 的動作.


mysql 的這個流程讓我嚇一跳,
以往大部份用的是 mssql 和 oracle,
在 client 和 server 間的 data cache 機制都做得比較好,
看起來 mysql 像是把結果算出來後就直接全部丟出來.

所以使用 mysql 要比用 mssql 或oracle 要來得更小心一點.

然後由於在上面的實驗裡我已經將 php.ini 的 memory_limit 設為 16M ,也用過 php -i 檢查過了。但在執行一百萬筆測試時,卻沒有受到任何影響,因此也證明了 shirock 說的「所以 mysql_query 查詢大量資料時,不會受到 memory_limit 的限制。

結論

從上面的實驗可以看到一次 Query 並取得資料總筆數雖是可行的,但這個前提是建立在 mysql_query 已經把資料內容全部放到 php.exe 的記憶體中。除非我誤解了 FIEND 的意思,不然他的作法看起來實在不適用於他所說的「存取大量資料的環境」。

所以一般常見的做法是採用就是 tokimeki 提到的兩次 Query 的方式,第一次先利用 SQL 的 COUNT() 指令取得我們所需要的總筆數,第二次再配合 LIMIT 去取得我們所要的資料。不用 LIMIT 的後果就是每當執行一次 script ,我們就要冒著記憶體使用量爆增的後果。

註:雖然 MySQL 有 Query Cache ,但對 php 端已經爆增的記憶體也於事無補了。

而我也贊成 tokimeki 說的:「另外之二,假設內容資料是非常龐大的,且必須利用查詢內容作某些運算(例如:矩陣運算之類的),那麼這樣的應用不該由PHP程式來完成,應由其他的方式來作計算(例如:資料庫作OLAP或是Server上某個用C/C++寫的程式定期跑),Web這邊只做顯示以及計算排程即可。」

其他不想多說了,被某人看不起也不是一天兩天的事了。我自知自己還有很多東西要學,而這些還有望其他高手前輩們給予我指教。

不過最後這個實驗也證明一件事:屁股不是用來想事情的。


Posted by jaceju at 16:21回應(15) PHP

2008年01月15日

[PHP-ZF] 初探 Zend_Db_Table Relationships (三)

前面我已經知道如何設定 Zend_Db_Table 的關連,這次來記一下串聯刪除 (cascading delete) 。

cascading delete 主要的目的是這樣的:假設 a 資料表和 b 資料表間有關連 (a.id = b.a_id) ,而當 a 資料表的某紀錄被刪除後,會使得 b 資料表中的相關紀錄失去作用而需要一併刪除。多數主流的資料庫系統都會內建類似的功能,例如 Oracle 、 MSSQL 及 PostgreSQL 等。但像是 MySQL 4.x 以前 的 MyISAM 格式並沒有提供 foreign key 和 trigger ,所以也沒辦法內建 Cascading Delete 時,這時只好從程式裡來著手。

不過有一點要小心,就算資料庫系統有支援 cascading delete ,也必須在資料表定義 foreign key 時設定 cascading delete 的相關指令 (通常是 ON DELETE CASCADE) ;不然資料表還是不會知道要去刪掉對應的紀錄。

註:以上資訊有錯誤的話,煩請大家不吝指正。

延續上一次的程式碼,這次我要為它們加入 cascading delete 的功能。

範例下載

不提供了,請下載上次的範例照以下說明修改吧。

說明

刪除文章時同時刪除文章與標籤之間的關連

首先我們在 Articles 這個類別中,加入一個 $_dependentTables 屬性:

<?php

class Articles extends Zend_Db_Table_Abstract
{
    // ... 略 ...

    protected $_dependentTables = array('ArticlesTags');
}

這個 $_dependentTables 必須給它一個陣列,這個陣列要包含你要串聯刪除的資料表類別名稱 (此例即為 ArticlesTags 這個類別) 。

接下來打開 ArticlesTags 這個類別,在原本的 $_referenceMap['Article'] 關連中,再加入一個 onDelete 索引:

<?php

class ArticlesTags extends Zend_Db_Table_Abstract
{
    protected $_referenceMap    = array(

        // ... 略 ...

        'Article' => array(
            'columns'           => array('article_id'),
            'refTableClass'     => 'Articles',
            'refColumns'        => 'id',
            'onDelete' => self::CASCADE,
        ),
    );
}

onDelete 的值固定就是用 self::CASCADE (其他值就不會動了) 。

註:別忘記原來 $_referenceMap['Article'] 所指定的意思,它是指 articles_tags 這個資料表的 articles_id (columns) 這個欄位要對應到 Articles (refTableClass) 這個資料表類別的 id 欄位 (refColumns) 。

這樣設定好後,就完成串聯刪除的動作了,很簡單吧?不過有一點非常重要:那就是一定要用 Zend_Db_Table_Row 的 delete 方法來刪除紀錄,才能啟動串聯刪除!

舉個例子:假設現在有兩篇文章,標題分別是 jQuery 和 Prototype ;現在我為它們都下了 javascript 和 ajax 兩個標籤。現在資料表紀錄如下:

articles 資料表
id category_id title
3 2 jQuery
4 2 Prototype
tags 資料表
id name
3 javascript
4 ajax
articles_tags 資料表
article_id category_id
3 3
3 4
4 3
4 4

註:請依照上面的例子,先在 articles 、 tags 及 artices_tags 三個資料表分別加入相關的紀錄。

接下來在 IndexController 中,加入一個 deleteAction 方法:

<?php

class IndexController extends Zend_Controller_Action 
{
    // ... 略 ...

    function deleteAction()
    {
        // 不顯示畫面
        $this->getHelper('ViewRenderer')->setNoRender();
        $articleTable = new Articles();
        // 有找到才刪
        if ($articleRow = $articleTable->find(3)->current()) {
            $articleRow->delete();
        }
    }
}

現在瀏覽一下 http://localhost/example3/index/delete (實際位置請按照各位的環境自行輸入) ,畫面應該不會出現任何資訊。現在到資料庫觀察一下,應該會發現 articles 資料表的第 3 筆紀錄已經被刪除;再看看 articles_tags ,在 article_id 為 3 的紀錄也跟著一併被刪除了。這樣就完成串聯刪除啦!

再整理一次重點:

  1. 在要刪除紀錄的資料表類別裡定義 $_dependentTables 。

  2. 在要串聯刪除紀錄的資料表類別裡定義 $_referenceMap[關連的名稱] 的 onDelete 屬性。

  3. 要使用串聯刪除,一定要用 Zend_Db_Row 的 delete 方法。


Posted by jaceju at 13:06回應(2)Zend Framework

2008年01月12日

[Web] 連結分享

PHP

Zend Framework

JavaScript

jQuery

  • Pagination plugin

    真是漂亮的分頁套件。

  • jQuery - Altering Layout Based On Dimensions

    太長的話就隱藏起來,再加上個下方導覽列。這個做法還滿讚的。

  • jquery-aop

    AOP 的大意是說可以在不破壞目前的程式狀況下,切入一些功能 (可能在動作的之前或之後) 。

  • jQuery FX

    官方網站竟然還沒有相關的訊息?還是我眼拙?不過我已經從 SVN 拉出來用了。

CSS

Web

Browser

Database

SQL

  • Why paging needs a lot of performance

    對呀,如果已經算過 COUNT(*) 了,為什麼不把它 Cache 起來呢?至少在不常更新資料的狀況下,這樣的做法可以減少一些 Loading ...

    而且就算有更新,一般來說筆數其實也不會一下子就超過每頁筆數,這樣所需要的總筆數就不用太精準。

Programming

Software


Posted by jaceju at 17:20回應(1)連結分享

2008年01月7日

dean 的 IE7 Library 釋出新版

完整文章連結:IE7.js version 2.0 (beta)

這次從 0.9 直接跳到 2.0 beta ,而且拆成了 IE7.js 和 IE8.js 兩個部份。

IE7.js 把功能只精簡到微軟正牌 IE7 有實作的部份,其他加強的部份就移到 IE8.js 上了 (因為 IE8 已經趕上 Web 標準了) 。其他修正部份如下:

  • The IE7 project is now hosted on googlecode (I got fed up with SourceForge).
  • IE7 is no longer modular. Instead I’ve merged the scripts into two: IE7.js and IE8.js
  • IE7.js includes only fixes that are included in the real MSIE7 browser.
  • All other enhancements are moved to IE8.js.
  • IE7 is now much smaller (11KB gzipped).
  • IE7 is now much faster (it uses the selector engine from base2.DOM)
  • There are no dependencies on other files (except blank.gif)
  • You can hotlink IE7/IE8.js directly from Google’s servers (usage instructions below)
  • The fix for base64 encoded images is no longer included

看起來不錯喔,有興趣的朋友不妨試一下。


Posted by jaceju at 10:13JavaScript

2008年01月4日

[Web] 連結分享

PHP

JavaScript

  • Add and Remove Elements with JavaScript (reprise)

    一個很簡單的 DOM 和 Event 教學,也可以算是一個超輕量級的 JS Library 。

  • Edit area

    線上編輯器,不過它是一個專門用來編輯「程式碼」且擁有語法多色顯示的編輯器。

  • PHP to Javascript Project: php.js

    把 PHP 的函式移植到 JavaScript 上,目前為止已經有 8 個函式已經實作出來了。

  • JavaScript, Regex, and Unicode

    在不同瀏覽器中, JavaScript 的 Regex 並不是把所有特殊字元類組或其他相關語法都是視為 Unicode 來處理。本文探討了這些字元類組在遇到 Unicode 字元時,會採取何種處理方式。

  • JavaScript: It's Just Not Validation!

    JavaScript 主要的目的是輔助使用者輸入正確資料,而不是用來驗證使用者的資料。

  • 防 SPAM 邮件地址

    不錯的方法,不過 document.write 不適用在 DOM 上,得另外想個方法解決。

jQuery

CSS

Web

Database

  • MySQL Table Prefix Changer Tool in PHP

    幫你在資料表更換 prefix ,測了一下,在一開始沒有 prefix 的狀況下會有 Bug 。解決方式:

    找到:

    function replace_prefix($s, $prefix) {
    	$pos = strpos($s, "_");
    	$s = substr($s, $pos + 1);
    	$s = sprintf("%s_%s", $prefix, $s);
    	return $s;
    }

    改成:

    function replace_prefix($s, $prefix) {
    	$pos = ($pos = strpos($s, "_")) ? $pos + 1 : 0;
    	$s = substr($s, $pos);
    	$s = sprintf("%s_%s", $prefix, $s);
    	return $s;
    }
  • How do you store a tree in a database table?

    如何把樹狀結構存在資料表裡?一般是加入一個 parent id ,本文則是把從父節點到根節點的所有 id 都存在一個欄位裡,並介紹如何用程式去處理它們之間的對應關係。 kiang 和 tokimeki 也討論過類似的問題,請參考:你還在用遞迴產生樹狀結構嗎?

Programming


Posted by jaceju at 17:15回應(5)連結分享
 [1]