傳統文件 Session 在多服務器環境下無法共享,頻繁 IO 且難以清理。本文提出基於 MySQL MEMORY 引擎的 Session 存儲方案,結合 Cookie 模擬原生 Session 行為,單 SQL 操作完成讀寫,並提供回收接口。
CREATE TABLE `*****_session` (
`sid` char(32) NOT NULL, -- Session ID (MD5)
`uid` int(10) NOT NULL, -- 用戶 ID,遊客為 0
`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, -- 客戶端 IP
`url` char(80) NOT NULL, -- 當前訪問 URL
`value` char(255) NOT NULL, -- Session 數據(序列化/JSON)
PRIMARY KEY (`sid`)
) ENGINE=MEMORY DEFAULT CHARSET=utf8;
| 字段 | 類型 | 設計考量 |
|---|---|---|
| ENGINE=MEMORY | 引擎 | 數據驻留內存,讀寫極快;重啟丟失但 Session 本身可丟失 |
| sid CHAR(32) | 主鍵 | 固定長度 MD5,索引高效;不使用 AUTO_INCREMENT 避免洩露用戶量 |
| value CHAR(255) | 數據 | 定長存儲,避免 VARCHAR 變長開銷;大數據可改 TEXT+單獨表 |
| activetime + expiry | 雙時間戳 | activetime 用於統計在線,expiry 用於判斷過期刪除 |
| ip + url | 統計字段 | 順便實現訪問日誌功能,一表多用 |
-- 根據 Cookie 中的 sid 查詢,同時更新活動時間
SELECT `value`, `uid`, `username`, `usertype`
FROM `session`
WHERE `sid` = ? AND `expiry` > UNIX_TIMESTAMP();
REPLACE INTO `session`
(`sid`, `uid`, `username`, `usertype`, `activetime`, `expiry`, `ip`, `url`, `value`)
VALUES (?, ?, ?, ?, UNIX_TIMESTAMP(), UNIX_TIMESTAMP()+3600, ?, ?, ?);
REPLACE 會刪除舊行插入新行,自動處理「不存在則插入、存在則更新」。
DELETE FROM `session` WHERE `sid` = ?;
DELETE FROM `session` WHERE `expiry` <= UNIX_TIMESTAMP();
class DbSession {
private $pdo;
private $sid;
private $lifetime = 3600;
public function __construct(PDO $pdo, $lifetime = 3600) {
$this->pdo = $pdo;
$this->lifetime = $lifetime;
$this->sid = $this->getSidFromCookie() ?: $this->generateSid();
$this->initCookie();
}
public function read() {
$stmt = $this->pdo->prepare("SELECT `value` FROM `session` WHERE `sid`=? AND `expiry`>?");
$stmt->execute([$this->sid, time()]);
return $stmt->fetchColumn() ? unserialize($stmt->fetchColumn()) : [];
}
public function write($data) {
$value = serialize($data);
$expiry = time() + $this->lifetime;
$stmt = $this->pdo->prepare("REPLACE INTO `session` (`sid`,`value`,`expiry`,`activetime`,`ip`,`url`) VALUES (?,?,?,?,?,?)");
$stmt->execute([$this->sid, $value, $expiry, time(), $_SERVER['REMOTE_ADDR'], $_SERVER['REQUEST_URI']]);
}
public function destroy() {
$stmt = $this->pdo->prepare("DELETE FROM `session` WHERE `sid`=?");
$stmt->execute([$this->sid]);
setcookie('SID', '', time()-3600, '/');
}
// 註冊為 PHP Session 處理器
public function register() {
session_set_save_handler(
[$this, 'open'], [$this, 'close'],
[$this, 'read'], [$this, 'write'],
[$this, 'destroy'], [$this, 'gc']
);
session_start();
}
}
| 優點 | 缺點/風險 |
|---|---|
| 極高讀寫性能(內存級) | MySQL 重啟數據全丟失(Session 可接受) |
| 天然支持多服務器共享 | MEMORY 表不支持 TEXT/BLOB,value 長度受限 |
| 單 SQL 操作,邏輯簡單 | 表級鎖,極高並發下可能阻塞(可分表緩解) |
| 順便實現用戶在線統計 | 需監控內存使用,防止 OOM |
這個方案在「無 Redis、有 MySQL、需共享 Session」的約束下是極佳選擇。理解其設計權衡,有助於在技術棧受限時做出最優決策。