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

解析 PHP session_set_save_handler 函數用法:MySQL 儲存 Session 實作

     編輯:PHP 綜合知識

PHP 自定義 Session 儲存處理器:MySQL 驅動完整實作

預設 Session 儲存在檔案系統,分散式部署、高併發場景下存在效能與一致性問題。使用 session_set_save_handler() 實現資料庫儲存,支援叢集共享、在線用戶統計、精準過期控制。

核心類別實作

<?php
class Session {
    private $db;
    private $lasttime = 3600; // 過期時間:1小時

    function __construct(&$db) {
        $this->db = &$db;
        session_module_name('user'); // 必須:宣告使用用戶自定義處理器
        
        session_set_save_handler(
            array(&$this, 'open'),     // session_start() 時執行
            array(&$this, 'close'),    // 腳本結束/ session_write_close() 時執行
            array(&$this, 'read'),     // session_start() 時讀取資料
            array(&$this, 'write'),    // 腳本結束/ session_write_close() 時寫入
            array(&$this, 'destroy'),  // session_destroy() 時執行
            array(&$this, 'gc')        // GC 觸發時執行 (機率由 gc_probability/divisor 決定)
        );
        session_start(); // 必須在註冊處理器後調用
    }

    // 解析 PHP 序列化 Session 資料
    function unserializes($data_value) {
        $vars = preg_split(
            '/([a-zA-Z_\x7f-\xff][a-zA-Z0-9_\x7f-\xff]*)\|/',
            $data_value, -1, PREG_SPLIT_NO_EMPTY | PREG_SPLIT_DELIM_CAPTURE
        );
        for ($i = 0; isset($vars[$i]); $i++) {
            $result[$vars[$i++]] = unserialize($vars[$i]);
        }
        return $result;
    }

    function open($path, $name) { return true; }

    function close() {
        $this->gc($this->lasttime);
        return true;
    }

    function read($sessionKey) {
        $sql = "SELECT uid FROM sessions WHERE session_id = '$sessionKey' LIMIT 1";
        $query = $this->db->query($sql);
        if ($row = $this->db->fetch_array($query)) {
            return $row['uid']; // 返回序列化資料 (這裡簡化只返回 uid)
        }
        return "";
    }

    function write($sessionKey, $data) {
        // 重新連接資料庫 (長連接可能斷開)
        require_once('db_mysql_class.php');
        $db1 = new DbCom();
        $db1->connect(DB_SERVER, DB_SERVER_USERNAME, DB_SERVER_PASSWORD, DB_DATABASE);
        $db1->query("SET NAMES utf8");
        $this->db = $db1;

        $sessionData = $this->unserializes($data);
        $sql0 = "SELECT uid FROM sessions WHERE session_id = '$sessionKey' LIMIT 1";
        $query0 = $this->db->query($sql0);
        
        if ($this->db->num_rows($query0) <= 0) {
            // 新 Session
            if (isset($sessionData['webid']) && !empty($sessionData['webid'])) {
                $this->db->query("INSERT INTO `sessions` SET 
                    `session_id` = '$sessionKey', 
                    `uid` = '{$sessionData['webid']}', 
                    `last_visit` = '" . time() . "'");
            }
        } else {
            // 現有 Session:可選擇更新 last_visit
            // $this->db->query("UPDATE `sessions` SET `last_visit` = '" . time() . "' WHERE `session_id` = '$sessionKey'");
        }
        return true;
    }

    function destroy($sessionKey) {
        $this->db->query("DELETE FROM `sessions` WHERE `session_id` = '$sessionKey'");
        return true;
    }

    function gc($lifetime) {
        $this->db->query("DELETE FROM `sessions` WHERE UNIX_TIMESTAMP(NOW()) - `last_visit` > '$this->lasttime'");
        return true;
    }
}

資料表結構

CREATE TABLE `sessions` (
    `session_id` CHAR(32) NOT NULL PRIMARY KEY,
    `uid` INT(10) UNSIGNED NOT NULL DEFAULT 0,
    `last_visit` INT(10) UNSIGNED NOT NULL DEFAULT 0,
    INDEX `idx_last_visit` (`last_visit`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8;

php.ini 關鍵 Session 配置解析

配置項推薦值說明
session.save_handleruser啟用自定義處理器
session.save_path/tmp檔案模式路徑;user 模式下被忽略
session.gc_maxlifetime1440GC 判定過期秒數
session.gc_probability1
session.gc_divisor1000高流量建議 1000-5000
session.use_cookiesOn
session.use_only_cookiesOn防 Session Fixation
session.cookie_httponlyOn防 XSS 竊取 ID
session.hash_function1 (SHA-1)更安全
session.hash_bits_per_character50-9,a-v (較短 ID)

生命週期流程圖

session_start()
    ↓
open() → read() → [GC 執行] → 腳本執行
    ↓
腳本結束 / session_write_close()
    ↓
write() → close()
    ↓
session_destroy() → destroy()

注意事項

  • write() 中重新連接資料庫:長腳本執行期間連接可能超時斷開
  • read() 必須返回序列化字串,write() 接收序列化字串
  • 生產環境建議使用 PDO + Prepared Statements 防 SQL 注入
  • 高併發場景考慮 Redis / Memcached 做 Session 儲存

此實作展示了 Session 處理器的完整介面契約,可作為資料庫/快取儲存 Session 的參考基礎。

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