PHP 預設以檔案儲存 Session,session_start() 會對 Session 檔案加獨佔鎖。同一用戶並發多個請求(如 AJAX 多介面同時載入)時,後續請求會阻塞等待前一請求釋放鎖,導致頁面響應時間疊加。本文深度剖析機制、影響場景,並提供三種解決方案。
請求 A (耗時 2s) ──► session_start() [鎖定] ──► 業務邏輯 ──► session_write_close() [釋放] ──► 輸出 請求 B (耗時 1s) ──► session_start() [阻塞等待] ──► (等待 A 釋放) ──► 執行 ──► 釋放 ──► 輸出 請求 C (耗時 0.5s) ──► session_start() [阻塞等待] ──► (等待 B 釋放) ──► 執行 ──► 釋放 ──► 輸出 總響應時間 = 2 + 1 + 0.5 = 3.5s (串行) 預期並行時間 = max(2, 1, 0.5) = 2s
session_start() 的所有進入點(包含 API、WebSocket 握手)。session_write_close() (最簡單、零成本)<?php session_start(); // 讀取/寫入 Session 資料 $userId = $_SESSION['user_id']; $_SESSION['last_visit'] = time(); // ✅ 關鍵:業務邏輯開始前釋放鎖 session_write_close(); // 後續耗時操作:資料庫查詢、API 呼叫、檔案處理... sleep(2); // 模擬耗時操作 echo json_encode(['status' => 'ok', 'user_id' => $userId]); // ⚠️ 注意:此處已無法再讀寫 $_SESSION ?>
優點:零架構變更、即時生效。
缺點:鎖釋放後無法更新 Session;若後續需寫入,需重新 session_start() (會重新加鎖)。
| 引擎 | 特點 | 適用場景 |
|---|---|---|
Redis (phpredis + session.save_handler=redis) | 高效能、支援集群、原子操作 | 高併發、分散式部署、需共享 Session |
| Memcached | 簡單、多伺服器自動分散 | 中小型、純快取場景 |
| 資料庫 (MySQL/PostgreSQL) | 持久化、可查詢、ACID | 需稽核、合規、長期保留 |
| DynamoDB (AWS) | 全託管、無限擴展、低延遲 | AWS 雲原生架構 |
設定範例 (Redis):
; php.ini 或 .user.ini session.save_handler = redis session.save_path = "tcp://127.0.0.1:6379?auth=yourpass&database=0" ; 或多節點 ; session.save_path = "tcp://host1:6379,tcp://host2:6379?weight=1,2"
Redis/Memcached 以鎖機制 (Lua 腳本/GETSET) 實現細粒度鎖,並發效能遠超檔案鎖。
<?php // 唯讀開啟:不加鎖、不更新 last-modified session_start(['read_and_close' => true]); // PHP 7.0+ // 或 session_start(['lazy_write' => true]); // PHP 7.0+,僅在 $_SESSION 異動時寫入 // 適用:純讀取用戶身份、權限等不頻繁變更的資料 ?>
session_start() 耗時,超過 100ms 即為疑似阻塞。ab -n 100 -c 10 -C "PHPSESSID=xxx" https://example.com/api 觀察響應時間分佈。keyspace_hits/misses、expired_keys 確認 Session TTL 運作正常。Session 並發阻塞是 PHP 高併發效能的隱形殺手,「讀完即釋」或「換引擎」 是兩大核心解法,請根據專案架構成熟度選擇最適策略。