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

PHP Session 並發阻塞問題深度分析與解決方案

     編輯:PHP 相關知識

問題背景

PHP 默認使用文件存儲 Session (`session.save_handler = files`)。調用 `session_start()` 時,PHP 會對 Session 文件加**獨占鎖 (Exclusive Lock)**。同一客戶端 (同 Session ID) 並發請求會導致串行執行,嚴重影響響應速度。

阻塞機制演示

<?php
// page_a.php - 耗時 5 秒
session_start();
sleep(5);
echo "A done";
session_write_close();

// page_b.php - 並發請求
session_start(); // 此處阻塞,等待 page_a 釋放鎖
echo "B done";
?>

瀏覽器同時打開兩頁,B 頁需等待 5 秒才開始輸出。異步 AJAX 請求同理,全部串行化。

影響範圍

  • ✅ 同一客戶端 (同 Session ID) 多請求串行
  • ❌ 不同客戶端 (不同 Session ID) 互不影響
  • ❌ 不涉及 Session 的請求不受影響

解決方案

方案一:session_write_close() 釋放鎖 (最常用)

<?php
session_start();
// 讀取 Session 數據
$user = $_SESSION['user'];

// ✅ 立即釋放鎖,後續耗時操作不阻塞其他請求
session_write_close();

// 耗時操作:數據庫查詢、外部 API 調用、大文件處理...
sleep(3);
$heavyData = fetchHeavyData();

// ⚠️ 此後不能再寫入 $_SESSION,需重新 session_start() 才能寫入
// 但重新 start 會再次加鎖,建議將寫操作前置
?>

適用場景:讀取 Session 後執行耗時邏輯,無需寫回 Session。

方案二:使用 Redis/Memcached 存儲 Session

; php.ini
session.save_handler = redis
session.save_path = "tcp://127.0.0.1:6379?auth=password&database=0"
; 或 Memcached
session.save_handler = memcached
session.save_path = "127.0.0.1:11211"

優勢:
• 無文件鎖,天然支援高併發
• 分布式部署共享 Session
• 可配置 TTL 自動過期清理
• 性能遠超文件 IO

方案三:自定義 Session Handler (資料庫/分布式鎖)

<?php
class DatabaseSessionHandler implements SessionHandlerInterface {
    private $pdo;
    public function __construct(PDO $pdo) { $this->pdo = $pdo; }
    public function open($path, $name) { return true; }
    public function close() { return true; }
    public function read($id) {
        // SELECT data FROM sessions WHERE id = ? AND expire > NOW()
        // 無行鎖,使用行級鎖或樂觀鎖更新
    }
    public function write($id, $data) {
        // INSERT ... ON DUPLICATE KEY UPDATE ...
    }
    public function destroy($id) { /* DELETE */ }
    public function gc($maxlifetime) { /* DELETE expired */ }
}

$handler = new DatabaseSessionHandler($pdo);
session_set_save_handler($handler, true);
session_start();
?>

可在 `write` 中使用 `SELECT ... FOR UPDATE` 或版本號樂觀鎖實現細粒度併發控制。

方案四:異步請求區分 Session 需求

  • 不需要 Session 的 API:完全不調用 `session_start()`
  • 只讀 Session:讀完即 `session_write_close()`
  • 需寫入 Session:盡量合併寫操作,縮短鎖持有時間

現代框架實踐

檢測與監控

# 檢查 Session 文件數量與大小
ls -la /var/lib/php/sessions/ | wc -l
du -sh /var/lib/php/sessions/

# 監控請求耗時分佈 (Nginx access log)
awk '{print $NF}' /var/log/nginx/access.log | sort -n | uniq -c | sort -nr

總結

Session 文件鎖是 PHP 高併發性能殺手之一。核心原則:**縮短鎖持有時間、遷移至無鎖存儲 (Redis/DB)、按需啟動 Session**。現代項目應在架構層面統一解決,而非業務代碼到處插 `session_write_close()`。

  1. 上一篇文章:
  2. 下一篇文章:
Copyright © 程式碼之家 All Rights Reserved
框架默認行為優化建議
Laravel文件驅動,請求結束自動保存生產環境改用 Redis/Database 驅動
SymfonyNative PHP Session配置 `session.handler.service` 使用 Redis
ThinkPHP文件驅動配置 `session_handler` 為 Redis
Yii2Session 組件使用 `yii\redis\Session`