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

PHP 使用 MySQL 儲存 Session 完整實作:解決分散式部署 Session 共享問題

     編輯:PHP 相關知識

PHP 使用 MySQL 儲存 Session 完整實作:解決分散式部署 Session 共享問題

預設 PHP 將 Session 儲存於伺服器檔案系統,在多伺服器叢集環境下會導致 Session 不一致。本文提供完整的 MySQL 儲存 Session 實作方案,使用 PDO 預處理語句確保安全效能,解決分散式部署的 Session 共享難題。

實作環境

  • PHP 5.4+
  • MySQL 5.6+
  • PDO 擴展

一、資料表結構

CREATE TABLE `session` (
    `skey`   CHAR(32) CHARACTER SET ascii NOT NULL,
    `data`   TEXT COLLATE utf8mb4_bin,
    `expire` INT(11) NOT NULL,
    PRIMARY KEY (`skey`),
    KEY `index_session_expire` (`expire`) USING BTREE
) ENGINE=MyISAM DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_bin;

設計重點:

  • skey 使用 ASCII 字元集節省空間
  • data 使用 utf8mb4 支援完整 Unicode
  • expire 整數時間戳,配合索引快速清理過期資料
  • MyISAM 引擎效能優於 InnoDB 於純讀寫 Session 場景

二、核心 PHP 實作

<?php
// 資料庫連線常量
define('SESSION_DSN',      'mysql:host=localhost;dbname=db;charset=utf8mb4');
define('SESSION_USR',      'usr');
define('SESSION_PWD',      'pwd');
define('SESSION_MAXLIFETIME', get_cfg_var('session.gc_maxlifetime'));

// 建立 PDO 連線(持久連線提升效能)
function getConnection() {
    try {
        return new PDO(SESSION_DSN, SESSION_USR, SESSION_PWD, array(
            PDO::ATTR_PERSISTENT      => true,
            PDO::ATTR_ERRMODE         => PDO::ERRMODE_EXCEPTION,
            PDO::ATTR_EMULATE_PREPARES=> false,
        ));
    } catch (Exception $e) {
        return null;
    }
}

// 1. open - 開啟 Session
function sessionMysqlOpen($savePath, $sessionName) { return true; }

// 2. close - 關閉 Session
function sessionMysqlClose() { return true; }

// 3. read - 讀取 Session 資料
function sessionMysqlRead($sessionId) {
    try {
        $dbh = getConnection();
        $time = time();
        
        // 先檢查是否存在且未過期
        $stmt = $dbh->prepare('SELECT COUNT(*) AS `count` FROM `session` WHERE `skey` = ? AND `expire` > ?');
        $stmt->execute(array($sessionId, $time));
        if ($stmt->fetchColumn() == 0) return '';
        
        // 讀取資料
        $stmt = $dbh->prepare('SELECT `data` FROM `session` WHERE `skey` = ? AND `expire` > ?');
        $stmt->execute(array($sessionId, $time));
        return $stmt->fetchColumn() ?: '';
    } catch (Exception $e) { return ''; }
}

// 4. write - 寫入/更新 Session 資料
function sessionMysqlWrite($sessionId, $data) {
    try {
        $dbh = getConnection();
        $expire = time() + SESSION_MAXLIFETIME;
        // INSERT ... ON DUPLICATE KEY UPDATE 原子操作
        $stmt = $dbh->prepare('INSERT INTO `session` (`skey`,`data`,`expire`) VALUES (?,?,?) ON DUPLICATE KEY UPDATE `data`=?, `expire`=?');
        $stmt->execute(array($sessionId, $data, $expire, $data, $expire));
    } catch (Exception $e) { error_log($e->getMessage()); }
}

// 5. destroy - 刪除 Session
function sessionMysqlDestroy($sessionId) {
    try {
        $dbh = getConnection();
        $stmt = $dbh->prepare('DELETE FROM `session` WHERE `skey` = ?');
        $stmt->execute(array($sessionId));
        return true;
    } catch (Exception $e) { return false; }
}

// 6. gc - 清理過期 Session
function sessionMysqlGc($lifetime) {
    try {
        $dbh = getConnection();
        $stmt = $dbh->prepare('DELETE FROM `session` WHERE `expire` < ?');
        $stmt->execute(array(time()));
        return true;
    } catch (Exception $e) { return false; }
}

// 7. 自定義 Session ID 生成(避免多伺服器衝突)
function sessionMysqlId() {
    if (filter_input(INPUT_GET, session_name()) === '' && filter_input(INPUT_COOKIE, session_name()) === '') {
        try {
            $dbh = getConnection();
            $uuid = $dbh->query('SELECT UUID() AS uuid')->fetchColumn();
            session_id(str_replace('-', '', $uuid));
            return true;
        } catch (Exception $e) { return false; }
    }
}

// 統一啟動函數
function startSession() {
    session_set_save_handler(
        'sessionMysqlOpen',
        'sessionMysqlClose',
        'sessionMysqlRead',
        'sessionMysqlWrite',
        'sessionMysqlDestroy',
        'sessionMysqlGc'
    );
    register_shutdown_function('session_write_close');
    sessionMysqlId();
    session_start();
}
?>

三、使用方式

<?php
require_once 'session_mysql.php';
startSession(); // 替代原本的 session_start()

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

四、關鍵技術點解析

技術點說明
ON DUPLICATE KEY UPDATEMySQL 特有語法,單一查詢完成「不存在則插入、存在則更新」,避免競態條件
預處理語句防止 SQL 注入,PDO::ATTR_EMULATE_PREPARES=false 使用真實預處理
持久連線PDO::ATTR_PERSISTENT=true 複用連線,減少建立連線開銷
expire > time()讀取時同時檢查過期,避免回傳失效資料,GC 只需清理資料庫
UUID() 生成 IDMySQL 原生 UUID 確保分散式環境下 ID 唯一性,避免衝突
register_shutdown_function確保腳本結束時正確寫入 Session,異常終止也能保存

五、效能優化建議

  1. 記憶體引擎:極高併發場景可嘗試 ENGINE=MEMORY,重啟後資料遺失但效能極致。
  2. 分表策略:超大流量可依 skey 首字元分 16/256 表,降低單表鎖定爭用。
  3. 非同步 GC:關閉 PHP 自動 GC (session.gc_probability=0),改用 Crontab 定時清理。
  4. Redis 替代:更高效能場景建議直接使用 Redis 儲存 Session(支援 Cluster、持久化)。

六、何時需要 MySQL Session?

  • 多台 Web 伺服器負載平衡(Nginx/HAProxy/LVS)
  • 避免 NFS/GlusterFS 檔案共享的效能與一致性問題
  • 需要跨應用、跨域名共享 Session 狀態
  • 審計需求:完整保留 Session 歷史記錄

提醒:單機或低流量應用無需此方案,PHP 原生檔案 Session 效能更好且零配置。架構升級應基於實際壓力測試數據決策,避免過度設計。

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