傳統資料庫 Session 方案存在兩大問題:自定義處理器實際應用價值低、Session 回收需額外處理;而完全避免 Session 改用頻繁資料庫操作則帶來嚴重效能瓶頸。本文提出一套整合統計、單 SQL 操作、延遲 GC 的優化方案。
CREATE TABLE `prefix_session` (
`sid` char(32) NOT NULL,
`uid` int(10) NOT NULL,
`username` char(32) NOT NULL,
`usertype` tinyint(1) NOT NULL,
`activetime` int(10) NOT NULL,
`expiry` int(10) NOT NULL,
`ip` char(15) NOT NULL,
`url` char(80) NOT NULL,
`value` char(255) NOT NULL,
PRIMARY KEY (`sid`)
) ENGINE=MEMORY DEFAULT CHARSET=utf8;
使用 MEMORY 引擎極大提升讀寫速度,`value` 欄位存序列化資料,長度限制 255 字元。
class Session {
private $_prefix = 'sess_';
private $_time;
private $_model; // 資料庫模型實例
private $_expiry = 1200; // 預設 20 分鐘
private $_domain = '';
protected $isNew = 0; // 0=更新 1=新增
protected $session = []; // 當前 Session 記錄
public function __construct($options) {
$this->_setOptions($options);
$this->_time = $this->_time ?? time();
$this->session['activetime'] = $this->_time;
}
public function start() { $this->_getSid(); }
public function set($key, $value) {
$coreKeys = ['uid','username','usertype','url','expiry'];
if (in_array($key, $coreKeys)) {
if ($key === 'expiry') {
$this->_setCookie($this->_prefix.'_sid', $this->session['sid'], $value);
$this->_setCookie($this->_prefix.'_uid', $this->session['uid'], $value);
}
$this->session[$key] = $value;
} else {
$other = $this->session['value'] ?? [];
$other[$key] = $value;
$this->session['value'] = $other;
}
}
public function get($key) {
$coreKeys = ['uid','username','usertype','url','expiry'];
if (in_array($key, $coreKeys)) return $this->session[$key] ?? null;
return $this->session['value'][$key] ?? null;
}
// 延遲 GC:透過檔案時間戳控制頻率
public function gc($file, $time = 1200) {
$last = @file_get_contents($file) ?: 0;
if ($last + $time < $this->_time) {
file_put_contents($file, $this->_time);
return $this->_model->delete('activetime + expiry < ' . $this->_time);
}
return false;
}
public function destroy() {
$this->session['uid'] = 0;
$this->session['username'] = '';
$this->session['usertype'] = -1;
$this->session['expiry'] = $this->_expiry;
$this->session['value'] = [];
$this->_setCookie($this->_prefix.'_sid', $this->session['sid'], $this->_expiry);
$this->_setCookie($this->_prefix.'_uid', $this->session['uid'], $this->_expiry);
}
public function __destruct() { $this->_save(); }
private function _save() {
$dbSession = $this->session;
$dbSession['value'] = serialize($dbSession['value']);
if (strlen($dbSession['value']) > 255) $this->_error('Session value too long!');
if ($this->isNew) {
$this->_model->insert($dbSession);
} else {
$sid = $dbSession['sid'];
unset($dbSession['sid']);
$this->_model->update($dbSession, "sid='$sid'");
}
}
private function _getSession($sid) {
$row = $this->_model->detail("sid='$sid'");
if (!$row) return false;
$row['value'] = unserialize($row['value']);
$this->session = array_merge($row, $this->session);
return true;
}
private function _getSid() {
$sid = strip_tags($_COOKIE[$this->_prefix.'_sid'] ?? '');
if (strlen($sid) === 32 && $this->_getSession($sid)) {
$this->isNew = 0;
} else {
$this->session['sid'] = md5(uniqid('', true));
$this->session['uid'] = 0;
$this->session['username'] = '';
$this->session['usertype'] = -1;
$this->session['expiry'] = $this->_expiry;
$this->session['value'] = [];
$this->isNew = 1;
$this->_setCookie($this->_prefix.'_sid', $this->session['sid'], $this->_expiry);
}
}
private function _setCookie($name, $value, $expire) {
setcookie($name, $value, $this->_time + $expire, '/', $this->_domain);
}
}