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

MySQL Session 儲存優化方案:單 SQL 操作、Cookie 模擬與 GC 策略

     編輯:PHP 相關知識

背景與痛點

傳統資料庫 Session 方案存在兩大問題:自定義處理器實際應用價值低、Session 回收需額外處理;而完全避免 Session 改用頻繁資料庫操作則帶來嚴重效能瓶頸。本文提出一套整合統計、單 SQL 操作、延遲 GC 的優化方案。

核心設計思路

  1. Session 與統計合一:遊客亦建立記錄,統一管理。
  2. 純資料庫 + Cookie 模擬 Session:不依賴 PHP 原生 Session 機制。
  3. 單 SQL 完成讀寫:用戶操作 Session 時盡量單條 SQL 完成;無操作時零查詢。
  4. GC 解耦:回收邏輯不內建,提供介面供外部定時任務呼叫。

資料表結構 (MEMORY 引擎)

CREATE TABLE `prefix_session` (
  `sid` char(32) NOT NULL,
  `uid` int(10) NOT NULL,
  `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,
  `url` char(80) NOT NULL,
  `value` char(255) NOT NULL,
  PRIMARY KEY (`sid`)
) ENGINE=MEMORY DEFAULT CHARSET=utf8;

使用 MEMORY 引擎極大提升讀寫速度,`value` 欄位存序列化資料,長度限制 255 字元。

PHP 核心類實作

class Session {
    private $_prefix = 'sess_';
    private $_time;
    private $_model;           // 資料庫模型實例
    private $_expiry = 1200;   // 預設 20 分鐘
    private $_domain = '';
    protected $isNew = 0;      // 0=更新 1=新增
    protected $session = [];   // 當前 Session 記錄

    public function __construct($options) {
        $this->_setOptions($options);
        $this->_time = $this->_time ?? time();
        $this->session['activetime'] = $this->_time;
    }

    public function start() { $this->_getSid(); }

    public function set($key, $value) {
        $coreKeys = ['uid','username','usertype','url','expiry'];
        if (in_array($key, $coreKeys)) {
            if ($key === 'expiry') {
                $this->_setCookie($this->_prefix.'_sid', $this->session['sid'], $value);
                $this->_setCookie($this->_prefix.'_uid', $this->session['uid'], $value);
            }
            $this->session[$key] = $value;
        } else {
            $other = $this->session['value'] ?? [];
            $other[$key] = $value;
            $this->session['value'] = $other;
        }
    }

    public function get($key) {
        $coreKeys = ['uid','username','usertype','url','expiry'];
        if (in_array($key, $coreKeys)) return $this->session[$key] ?? null;
        return $this->session['value'][$key] ?? null;
    }

    // 延遲 GC:透過檔案時間戳控制頻率
    public function gc($file, $time = 1200) {
        $last = @file_get_contents($file) ?: 0;
        if ($last + $time < $this->_time) {
            file_put_contents($file, $this->_time);
            return $this->_model->delete('activetime + expiry < ' . $this->_time);
        }
        return false;
    }

    public function destroy() {
        $this->session['uid'] = 0;
        $this->session['username'] = '';
        $this->session['usertype'] = -1;
        $this->session['expiry'] = $this->_expiry;
        $this->session['value'] = [];
        $this->_setCookie($this->_prefix.'_sid', $this->session['sid'], $this->_expiry);
        $this->_setCookie($this->_prefix.'_uid', $this->session['uid'], $this->_expiry);
    }

    public function __destruct() { $this->_save(); }

    private function _save() {
        $dbSession = $this->session;
        $dbSession['value'] = serialize($dbSession['value']);
        if (strlen($dbSession['value']) > 255) $this->_error('Session value too long!');

        if ($this->isNew) {
            $this->_model->insert($dbSession);
        } else {
            $sid = $dbSession['sid'];
            unset($dbSession['sid']);
            $this->_model->update($dbSession, "sid='$sid'");
        }
    }

    private function _getSession($sid) {
        $row = $this->_model->detail("sid='$sid'");
        if (!$row) return false;
        $row['value'] = unserialize($row['value']);
        $this->session = array_merge($row, $this->session);
        return true;
    }

    private function _getSid() {
        $sid = strip_tags($_COOKIE[$this->_prefix.'_sid'] ?? '');
        if (strlen($sid) === 32 && $this->_getSession($sid)) {
            $this->isNew = 0;
        } else {
            $this->session['sid'] = md5(uniqid('', true));
            $this->session['uid'] = 0;
            $this->session['username'] = '';
            $this->session['usertype'] = -1;
            $this->session['expiry'] = $this->_expiry;
            $this->session['value'] = [];
            $this->isNew = 1;
            $this->_setCookie($this->_prefix.'_sid', $this->session['sid'], $this->_expiry);
        }
    }

    private function _setCookie($name, $value, $expire) {
        setcookie($name, $value, $this->_time + $expire, '/', $this->_domain);
    }
}

關鍵優化細節

  • 核心欄位分離:`uid`、`username` 等常用欄位獨立欄位,避免每次反序列化 `value`。
  • Cookie 雙鍵同步:`sid` 與 `uid` 同步寫入 Cookie,便於快速識別用戶身份。
  • 序列化長度檢查:防止 `value` 超過 255 字元導致截斷。
  • GC 檔案鎖機制:以檔案 `mtime` 控制 GC 執行頻率,避免每請求掃表。

部署建議

  1. 確認 MySQL 支援 MEMORY 引擎且 `max_heap_table_size` 足夠大。
  2. 生產環境建議將 GC 呼叫移至獨立 Cron 任務(如每 5 分鐘執行一次)。
  3. 若 Session 資料量極大,考慮改用 Redis/Memcached 做二級快取。
  1. 上一篇文章:
  2. 下一篇文章:
Copyright © 程式碼之家 All Rights Reserved