先聲明,這一篇不是我原創的,主要程式是從 "PHP用mysql資料庫存儲session" 引用而來,我只是拿來再修改一下,並做一個自我記錄
我先說一下我的版本 Apache 2.2.4 ; PHP 5.2.3 (WAMP);另一台是 Apache 2.x; PHP 5.4 (CentOS)。資料庫都是MySql
實際使用的心得,並沒有感到網站跑的比較快,似乎還有一點慢下來。但原因可能是我的網站目前人數很少。讀取Mysql反而花一點點時間。但我認為長期而言把session存放到資料庫(或memached)是正確的方向,假設網站有兩萬人瀏覽,那麼從兩萬個session暫存檔找出來的效率,絕對比從一個database table找出來要來的慢。如果網站一開始就考量大量人數的話,那麼database是好選擇。但換句話,如果網站一個月不到一千人瀏覽,那麼就沒有什麼必要性去做這個動作。
現在來講步驟了:
(1)"不要"更改php.ini檔裏面的 session.save_handler
(2) 在mysql裏創立一個table。這裏我用的是UFT8編碼 (很多網路教學用的是latin1),不是因為session會用到中文,而是整個資庫庫的其他table有可能用到中文,在連線一致性的狀況下,我乾脆把所有table都設成UTF8編碼形式,以方便未來的連線。另外,底下的`value` text,如果你有大量文檔儲存的需要,也可以改成`value` mediumtext。
CREATE TABLE `db_session` (
`sesskey` char( 32 ) COLLATE utf8_unicode_ci NOT NULL ,
`expiry` int( 11 ) unsigned NOT NULL ,
`value` mediumtext COLLATE utf8_unicode_ci NOT NULL ,
PRIMARY KEY ( `sesskey` )
) ENGINE = InnoDB DEFAULT CHARSET = utf8 COLLATE = utf8_unicode_ci;
(3)建立 session_mysql.php 檔案,檔案內容如下,請存成session_mysql.php檔案,底下的xxxx請置換成你自己的資訊,
這裏的檔案連線採用pconnect,這也是我欣賞原創者的原因,很多其他教學都採用mysql_connet,但這裏採用mysql_poconnet,我查了一下,mysql_poconnect有兩個和my_connect不同的特點 (a)它會先搜尋我們目前的host是否已經有採用相同資料庫連線存在,如果有,它就不會執行,以避免重覆連線導致其他連線失敗(Mysql-Link)的情形。(b)即使script執行完,這個連結會繼續保存,不會刪掉,它是一種persistent(持續保存)的狀態,不會被mysql_close()關掉,如果你採用了PEAR等延伸套件,然後套件也有用到session的話,那麼這個功能就派上用場,否則PEAR可能會在seeion宣告時因為session資料庫連線失敗而失效。
如果我們把底下的mysql_pcconnet改成mysql_connect也可以,但,你就必需確保你的程式,完全不會再宣告其他相同的mysql_connect。
<?php
$gb_DBname="xxxxx";//資料庫名稱
$gb_DBuser="xxxxx";//資料庫使用者名稱
$gb_DBpass="xxxxx";//資料庫密碼
$gb_DBHOSTname="xxxxx";//主機的名稱或是IP位址
$SESS_DBH="";
$SESS_LIFE=get_cfg_var("session.gc_maxlifetime");//得到session的最大有效期。
function sess_open($save_path,$session_name){
global $gb_DBHOSTname,$gb_DBname,$gb_DBuser,$gb_DBpass,$SESS_DBH;
//pcconnect是永遠連線的意思,但如偵測到有其他mysql_connect到同樣位置,就會選擇不執行以避免重覆連線
if(!$SESS_DBH=mysql_pconnect($gb_DBHOSTname,$gb_DBuser,$gb_DBpass)){
echo "<li>MySql Error:".mysql_error()."<li>";
die();
}
if(!mysql_select_db($gb_DBname,$SESS_DBH)){
echo "<li>MySql Error:".mysql_error()."<li>";
die();
}
//設定連線的文字集與校對為 UTF8 編碼,沒有這一行,不只此處,所有網頁SELECT出來的中文會變成"????"。如果用mysql_query("set character_set_results='utf8'")也可以,但沒SET NAMES utf8廣泛有效。相關說明http://www.vixual.net/blog/archives/310
mysql_query("SET NAMES utf8");
return true;
}
function sess_close(){
//不像其他網路方法,這裏選擇不關閉資料庫連線,因為我們會持續用到
return true;
}
function sess_read($key){
global $SESS_DBH,$SESS_LIFE;
$qry="select value from db_session where sesskey = '$key' and expiry > ".time();
$qid=mysql_query($qry,$SESS_DBH);
if(list($value)=mysql_fetch_row($qid)){
return $value;
}
return false;
}
function sess_write($key,$val){
global $SESS_DBH,$SESS_LIFE;
$expiry=time()+$SESS_LIFE;
$value=$val;
$qry="insert into db_session (sesskey,expiry,value) values('$key',$expiry,'$value')";
$qid=mysql_query($qry,$SESS_DBH);
if(!$qid){
$qry="update db_session set expiry=$expiry, value='$value' where sesskey='$key' and expiry >".time();
$qid=mysql_query($qry,$SESS_DBH);
}
return $qid;
}
function sess_destroy($key){
global $SESS_DBH;
$qry="delete from db_session where sesskey = '$key'";
$qid=mysql_query($qry,$SESS_DBH);
return $qid;
}
function sess_gc($maxlifetime){
global $SESS_DBH;
$qry="delete from db_session where expiry < ".time();
$qid=mysql_query($qry,$SESS_DBH);
return mysql_affected_rows($SESS_DBH);
}
session_module_name();
//session_set_save_handler是整個程式重點,因為它是宣告session各種讀取存放...等的class,會覆蓋原PHP設定
session_set_save_handler("sess_open","sess_close","sess_read","sess_write","sess_destroy","sess_gc");
?>
(4) 在你所需要用到的檔案裏,執行 require_once('session_mysql.php');
但有幾個原則
(4.1)要在 session_start() 之前
(4.2)要在PEAR套件之前 (因為PEAR內也許包括了session_start()
(4.3)只要改你的檔案,無需太雞婆的去改PEAR套件內的東西,硬去加上require_once('session_mysql.php');理由是因為這種讀取只要一次就好了,你多加在PEAR內,會造成重覆宣告class的情形發生。但,也有例外,比如我有使用captcha套件,但它的啟動方式是放在javascript裏,可能因此和require方式不同,所以這種情形之下,實驗結果是也要放一行require_once('session_mysql.php');在captcha.php裏面才能。總之,你實驗看看應該就知道何種情況該用或不該用了。
例子如下:
<?php
require_once('session_mysql.php'); //先引入session_mysql,讓session_set_save_handler生效,此時session的讀取寫入等功能已被改寫
require_once('AUTH.php'); //讀入PEAR等套件
session_start(); //啟動session功能
..................底下省略........... ?>
你可以隨手寫一下 $_SESSION['user'] = 'TESTUSER'; 或unset($_SESSION['user']); 或 session_unset();或 session_destroy(); (不要傻傻放在同一頁執行啊), 來觀察資料庫裏的de_session裏面的值是否有改變,如果有,就是成功了。