問題本質
「查庫存 → 判斷 >0 → 扣減」非原子操作。高併發下多進程同時讀到庫存 >0,均執行扣減,導致庫存變負(超賣)。
常見方案對比
| 方案 | 原理 | 優點 | 缺點 |
| 單進程隊列 | 請求入隊,消費者串行處理 | 徹底串行化 | 需額外進程、有延遲、架構複雜 |
| 樂觀鎖 | 查庫存+版本號,更新時比對版本 | 無鎖、高併發友好 | 重試邏輯複雜、寫衝突多時性能差 |
| SQL 條件更新 | UPDATE ... SET stock=stock-1 WHERE id=1 AND stock>0 | 原子級、極簡 | 需依賴資料庫行鎖、死鎖風險 |
| 文件排他鎖 flock | 進程級串行化關鍵區 | 輕量、無額外組件、跨資料庫 | 單機限制、性能上限受文件系統影響 |
flock 方案代碼實現
阻塞模式(等待鎖釋放)
非阻塞模式(立即返回,忙則拒絕)
關鍵注意點
lock.txt 需預先建立並可寫,建議放在項目 Runtime/Lock/ 目錄。
- 鎖粒度:全局單文件鎖簡單但並發上限低(~幾百 QPS)。高併發建議按商品 ID 分文件鎖
lock_{$goods_id}.txt。
- 務必
fclose 釋放文件句柄,否則鎖長期持有導致死結。
- 分佈式/多機部署需升級為 Redis 分佈式鎖 (
SET NX EX) 或數據庫行鎖,flock 僅適用單機。
進階:結合 SQL 條件更新雙重保障
if (flock($fp, LOCK_EX | LOCK_NB)) {
$affected = $db->execute("UPDATE goods SET stock=stock-1 WHERE id={$id} AND stock>0");
if ($affected) { /* 成功 */ }
else { /* 庫存不足 */ }
flock($fp, LOCK_UN);
}
應用層鎖 + 數據庫層條件更新,雙重保險,極大降低超賣風險。
- 上一篇文章: PHP base64_decode 解碼亂碼根因分析:GET 傳遞時 + 被轉為空格
- 下一篇文章: PHP 截取含 HTML 標簽字符串函數:自動閉合標簽、支援錨點截斷、多字節安全
Copyright ©
程式碼之家 All Rights Reserved