首頁
編程語言
C語言|JAVA編程
Python編程
網頁編程
ASP編程|PHP編程
JSP編程
數據庫
MYSQL數據庫|SqlServer數據庫
Oracle數據庫|DB2數據庫
您现在的位置: 程式碼之家 >> 編程語言 >  >> 網頁程式設計 >> PHP 程式設計 >> PHP 綜合知識

PHP 自定義 Session 處理器:以資料庫儲存 Session 完整實作

     編輯:PHP 綜合知識

預設 PHP Session 以檔案儲存,無法支援多伺服器負載均衡、難以實作單點登入、清理策略不可控。透過 session_set_save_handler() 註冊自定義儲存引擎,將 Session 寫入 MySQL/Redis 即可解決上述問題。本文提供基於 PDO 的資料庫 Session 類完整實作。

資料表結構

CREATE TABLE `sessions` (
    `session_id` VARCHAR(128) NOT NULL PRIMARY KEY,
    `session_data` TEXT NOT NULL,
    `expire_time` INT UNSIGNED NOT NULL,
    `created_at` TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
    `updated_at` TIMESTAMP DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;

核心類別:PdoSessionHandler

<?php

class PdoSessionHandler implements SessionHandlerInterface
{
    private \PDO $pdo;
    private int $lifetime;

    public function __construct(\PDO $pdo, int $lifetime = 3600)
    {
        $this->pdo = $pdo;
        $this->lifetime = $lifetime;
        // 關鍵:禁止 PHP 內建檔案儲存
        ini_set('session.save_handler', 'user');
        session_set_save_handler($this, true);
    }

    /** 開啟 Session,回傳 true 表示成功 */
    public function open(string $path, string $name): bool
    {
        return true; // PDO 連線已在建構子建立
    }

    /** 關閉 Session,寫入完成後自動呼叫 */
    public function close(): bool
    {
        return true;
    }

    /** 讀取 Session 資料 */
    public function read(string $id): string|false
    {
        $stmt = $this->pdo->prepare(
            'SELECT session_data FROM sessions WHERE session_id = ? AND expire_time > ?'
        );
        $stmt->execute([$id, time()]);
        $row = $stmt->fetch(\PDO::FETCH_COLUMN);
        return $row !== false ? $row : '';
    }

    /** 寫入/更新 Session 資料 */
    public function write(string $id, string $data): bool
    {
        $expire = time() + $this->lifetime;
        $stmt = $this->pdo->prepare(
            'INSERT INTO sessions (session_id, session_data, expire_time)
             VALUES (?, ?, ?)
             ON DUPLICATE KEY UPDATE session_data = VALUES(session_data), expire_time = VALUES(expire_time)'
        );
        return $stmt->execute([$id, $data, $expire]);
    }

    /** 銷毀 Session (登出) */
    public function destroy(string $id): bool
    {
        $stmt = $this->pdo->prepare('DELETE FROM sessions WHERE session_id = ?');
        return $stmt->execute([$id]);
    }

    /** 垃圾回收:清理過期資料 */
    public function gc(int $maxlifetime): int|false
    {
        $stmt = $this->pdo->prepare('DELETE FROM sessions WHERE expire_time < ?');
        $stmt->execute([time()]);
        return $stmt->rowCount();
    }

    /** PHP 7.0+ 新介面:建立新 Session ID */
    public function create_sid(): string
    {
        return bin2hex(random_bytes(16));
    }

    /** PHP 7.0+ 新介面:驗證 Session ID 格式 */
    public function validateId(string $id): bool
    {
        return (bool)preg_match('/^[a-zA-Z0-9,\-]{22,256}$/', $id);
    }

    /** PHP 7.0+ 新介面:更新 Session 最後存取時間 */
    public function updateTimestamp(string $id, string $data): bool
    {
        return $this->write($id, $data);
    }
}

啟用方式

<?php
require_once 'PdoSessionHandler.php';

$pdo = new PDO('mysql:host=localhost;dbname=app;charset=utf8mb4', 'user', 'pass', [
    PDO::ATTR_ERRMODE => PDO::ERRMODE_EXCEPTION,
    PDO::ATTR_DEFAULT_FETCH_MODE => PDO::FETCH_ASSOC,
]);

// 啟動自定義處理器(必須在 session_start() 之前)
new PdoSessionHandler($pdo, 7200); // 2 小時過期

session_start();

// 正常使用 $_SESSION
$_SESSION['user_id'] = 123;
$_SESSION['username'] = 'admin';

進階優化建議

  • 改用 Redis:高併發場景下,Redis 記憶體操作比 MySQL 快 10-100 倍,且原生支援 TTL 自動過期,無需 GC 掃表。只需將 `read/write/destroy/gc` 改為 Redis 指令即可。
  • 分散式鎖防併發寫入:若同一 Session 可能並發請求 (AJAX 多請求),建議在 `write` 使用 SELECT ... FOR UPDATE 或 Redis SETNX 加鎖。
  • Session 資料序列化:預設 PHP `serialize()` 格式不利於跨語言共享。可改用 `json_encode` / `igbinary`,並在 `read` 時對應解碼。
  • 定期清理策略:MySQL 方案中 `gc` 由 PHP 隨機觸發 (預設 1% 機率)。生產環境建議關閉 `session.gc_probability=0`,改用 Crontab 定時 DELETE FROM sessions WHERE expire_time < UNIX_TIMESTAMP()。

常見問題排查

現象可能原因解決
Session 無法寫入資料表欄位長度不足 / 編碼不匹配session_data 用 TEXT/MEDIUMTEXT,資料庫連線 charset=utf8mb4
頻繁掉線GC 機率過高 / lifetime 過短調大 session.gc_maxlifetime,關閉隨機 GC 改用定時任務
多伺服器 Session 不共享未共用同一資料庫/Redis確保所有節點指向同一 Session 儲存後端

掌握自定義 Session 處理器,是 PHP 開發者邁向分散式架構、高可用系統的必修課。

  1. 上一篇文章:
  2. 下一篇文章:
Copyright © 程式碼之家 All Rights Reserved