file_put_contents() 默认覆盖写入且无锁,高并发下易因竞态丢失数据;应使用flock()加锁(带超时重试)或原子rename()替换;追加日志宜用'a'模式+LOCK_EX;跨进程同步需配合inotify或信号通知。

直接用 file_put_contents() 写文件会丢数据
这不是 bug,是设计使然:file_put_contents($file, $data) 默认覆盖写入,中间没有锁。两个请求几乎同时执行,就会出现“读旧→改→写”竞态:后写入的覆盖先写入的,丢失一次更新。
用 flock() 加锁但别裸用
flock() 是建议性锁,依赖所有进程主动配合;它在 NFS 上不可靠,且阻塞不设超时容易卡死。实操必须带超时和异常兜底:
- 打开文件后立即
flock($fp, LOCK_EX | LOCK_NB),LOCK_NB避免无限等待 - 失败时重试最多 3 次,每次
usleep(50000)(50ms) - 成功后务必在
fclose()前调用flock($fp, LOCK_UN),否则锁残留 - 不要对
file_put_contents()的临时句柄加锁——它内部已 fclose,锁无效
高并发下优先用原子替换而非原地修改
比加锁更稳的方案是绕过锁:生成临时文件 → 写完 → rename() 替换原文件。前提是目标路径和 sys_get_temp_dir() 在同一文件系统分区。
-
$tmp = tempnam(sys_get_temp_dir(), 'update_');确保临时名唯一 - 写入后用
file_exists($tmp) && filesize($tmp) === strlen($new_content)校验完整性 -
rename($tmp, $file)失败时,unlink($tmp)清理,不扔垃圾 - 注意:Windows 下
rename()对正在被其他进程打开的文件会失败,需确保无 long-running 进程 hold 句柄
追加日志类场景用 'a' 模式 + LOCK_EX
纯末尾追加(如访问日志)不需要读旧内容,fopen($file, 'a') 自动定位到 EOF,但并发写仍需排他锁防错位:
立即学习“PHP免费学习笔记(深入)”;
- 必须用
fopen($file, 'a'),不是'w'或'c';后者会截断或不自动定位 -
fwrite($fp, $line . "\n")后无需flock($fp, LOCK_UN)—— 关闭前锁自动释放 -
file_put_contents($file, $line, FILE_APPEND | LOCK_EX)更简洁,但 PHP 8.2+ 才保证LOCK_EX在追加时生效 - 避免在循环里反复
fopen/fclose,复用句柄能显著降低系统调用开销



















