PHP 預設的 Session 機制為同一客戶端同一時間只能有一個請求持有 Session 鎖。當多個 AJAX 請求並發訪問同一 Session 時,後續請求會阻塞等待前一請求釋放鎖(預設在腳本結束或顯式調用 session_write_close() 時釋放)。本文通過實測代碼演示阻塞現象,並給出生產環境可用的優化模式。
<script src="https://cdn.jsdelivr.net/npm/jquery@3.6.0/dist/jquery.min.js"></script>
<script>
$(function() {
for (var i = 1; i <= 5; i++) {
$.ajax({
url: 'rest.php?call=' + i,
success: function(data) {
$('body').append('<p>Call complete ' + data + '</p>');
}
});
}
});
</script>
<?php
session_start();
sleep(2); // 模擬業務邏輯耗時
echo '{json:"response ' . $_GET["call"] . '"}';
?>
結果:5 請求串行執行,總耗時 ≈ 10 秒(5 × 2s)。瀏覽器 Network 面板顯示請求依次完成,而非並行。
<?php
session_start();
// 讀取/寫入 Session 相關操作完成後,立即釋放鎖
session_write_close();
sleep(2); // 耗時業務邏輯,此時其他請求可並行進入
echo '{json:"response ' . $_GET["call"] . '"}';
?>
結果:總耗時 ≈ 2 秒(並發執行)。Session 數據在 session_write_close() 時寫入存儲引擎(files/redis/memcached),後續代碼不再持有鎖。
<?php
// 1. 啟動 Session
session_start();
// 2. 讀取必要的 Session 數據到局部變量
$userId = $_SESSION['user_id'] ?? null;
$permissions = $_SESSION['perms'] ?? [];
// 3. 【關鍵】業務邏輯無需再寫入 Session 時,立即釋放鎖
session_write_close();
// 4. 執行耗時操作:DB 查詢、外部 API 呼叫、大量計算、sleep 等
// 此期間同用戶其他請求可並行處理
$result = heavyOperation($userId);
// 5. 若後續仍需寫入 Session(如更新最後活動時間),可重新啟動
// session_start();
// $_SESSION['last_active'] = time();
// session_write_close(); // 再次釋放
echo json_encode(['data' => $result]);
?>
| 場景 | 建議 |
|---|---|
| 純讀取 Session / 只在開頭寫入 | 啟動 → 讀/寫 → session_write_close() → 其餘邏輯 |
| 中間需多次讀寫 Session | 分段:session_start() → 操作 → session_write_close() → 耗時邏輯 → 再次 session_start() … |
| 使用 Redis/Memcached 存儲 Session | 鎖粒度通常較細(按 Key),但同 Key 仍會阻塞,同樣建議盡早釋放 |
| 長輪詢 / SSE / WebSocket | 握手階段驗證 Session 後立即 session_write_close(),長連接期間不持有鎖 |
若業務極度敏感於延遲,可實作 SessionHandlerInterface,在 read() 返回數據後不加鎖、write() 僅追加寫入(如 Redis Lua 腳本原子操作),但需自行處理並發一致性(樂觀鎖/版本號)。
Session 鎖是 PHP 併發性能的隱形殺手。遵循「用完即關」原則:session_start() → 讀寫 Session → session_write_close() → 執行耗時邏輯,能將串行阻塞轉為真正並行,顯著提升高併發場景下的響應速度與吞吐量。