預設 PHP 以檔案儲存 Session(session.save_handler = files),單機環境尚可,但在負載均衡、多伺服器叢集或容器化部署時,檔案系統無法共享,導致使用者頻繁被登出。將 Session 寫入資料庫(或 Redis/Memcached)即可實現跨節點共享,並便於集中管理、查詢與清理。
; 採用自定義儲存處理器
session.save_handler = user
或在程式啟動早期呼叫:
ini_set('session.save_handler', 'user');
CREATE DATABASE `session_db` CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci;
USE `session_db`;
CREATE TABLE `sessions` (
`id` CHAR(128) NOT NULL PRIMARY KEY,
`user_id` BIGINT UNSIGNED NULL DEFAULT NULL,
`data` MEDIUMBLOB NOT NULL,
`last_activity` INT UNSIGNED NOT NULL DEFAULT 0,
INDEX `idx_last_activity` (`last_activity`),
INDEX `idx_user_id` (`user_id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;
id:Session ID,長度 128 相容 PHP 7.1+ session.sid_length。user_id:關聯使用者,方便踢除特定用戶 Session。data:序列化後的 Session 資料。last_activity:最後活動時間戳,供 GC 清理過期資料。<?php
declare(strict_types=1);
class PdoSessionHandler implements SessionHandlerInterface
{
private PDO $pdo;
public function __construct(PDO $pdo)
{
$this->pdo = $pdo;
}
public function open($savePath, $sessionName): bool
{
return true; // PDO 連線已在外部建立
}
public function close(): bool
{
return true;
}
public function read($id): string|false
{
$stmt = $this->pdo->prepare('SELECT `data` FROM `sessions` WHERE `id` = ?');
$stmt->execute([$id]);
$row = $stmt->fetch(PDO::FETCH_ASSOC);
return $row ? $row['data'] : '';
}
public function write($id, $data): bool
{
$time = time();
$sql = <<pdo->prepare($sql);
return $stmt->execute([$id, $data, $time]);
}
public function destroy($id): bool
{
$stmt = $this->pdo->prepare('DELETE FROM `sessions` WHERE `id` = ?');
return $stmt->execute([$id]);
}
public function gc($maxLifetime): int|false
{
$expiry = time() - $maxLifetime;
$stmt = $this->pdo->prepare('DELETE FROM `sessions` WHERE `last_activity` < ?');
$stmt->execute([$expiry]);
return $stmt->rowCount();
}
}
// 註冊處理器
$pdo = new PDO('mysql:host=127.0.0.1;dbname=session_db;charset=utf8mb4', 'user', 'pass', [
PDO::ATTR_ERRMODE => PDO::ERRMODE_EXCEPTION,
PDO::ATTR_DEFAULT_FETCH_MODE => PDO::FETCH_ASSOC,
]);
$handler = new PdoSessionHandler($pdo);
session_set_save_handler($handler, true);
session_start();
// 後續正常使用 $_SESSION
$_SESSION['user_id'] = 123;
last_activity 超過 30 天)。session_set_save_handler() 必須在 session_start() 之前呼叫。read() 應回傳空字串而非 false,否則 PHP 會拋出警告。session_regenerate_id(true),舊 Session 資料需同步刪除或標記失效。將 Session 遷移至資料庫是水平擴展的關鍵一步。本文提供基於 PDO 的完整實作,可直接套用於生產環境;若追求極致效能,建議進一步改用 Redis Cluster 作為儲存後端。