PHP mkdir() 非原子操作,并发时易因竞态导致目录创建失败或结构不完整;推荐用 flock 加锁保障幂等创建,或改用 atomic flag 文件协调,生产环境宜预热目录、用对象存储或队列服务解耦。

PHP 中 mkdir() 本身不是原子操作,当多个请求并发执行 mkdir($path, 0755, true) 创建同一级嵌套目录(如 uploads/2026/11/03)时,极易触发竞态条件:两个进程几乎同时判断目录不存在 → 同时尝试创建 → 其中一个失败并抛出 Warning: mkdir(): File exists 或更糟地因权限/时序问题导致部分目录缺失、结构不完整。
根本原因:目录检查与创建分离
即使使用递归模式(true 参数),PHP 的 mkdir() 在底层仍需逐级检查父目录是否存在。若两个进程在检查 uploads/2026 存在后、创建 uploads/2026/11 前的极短窗口内交错执行,就可能造成中间目录被重复创建或跳过,尤其在 NFS、容器挂载卷等弱一致性文件系统上更明显。
推荐方案:用 flock + mkdir 组合实现幂等创建
不依赖“先判断再创建”的逻辑,而是用文件锁保护整个判断+创建流程,确保同一路径只被一个进程成功初始化:
- 选一个稳定、本地、可写且进程共用的锁文件(如
/tmp/mkdir_lock_uploads.bin),避免跨网络文件系统 - 打开锁文件并加独占锁(
flock($fp, LOCK_EX)),阻塞等待其他竞争者释放 - 锁内再次检查目录是否存在(防止被其他已获锁进程抢先创建)
- 仅当不存在时才调用
mkdir($path, 0755, true);无论成功与否,都继续执行后续逻辑 - 立即释放锁,不等到
fclose()
更健壮的替代:改用 atomic 文件写入间接保障
如果目标是为上传准备目录,真正需要的是“确保路径可用”,而非强求 mkdir 成功。可绕过目录创建本身,转而用原子文件操作建立存在性信号:
- 定义唯一标识路径的临时文件,如
/tmp/created_uploads_2026_11_03.flag - 用
file_put_contents($flag, '', LOCK_EX)尝试原子写入(该函数底层调用flock) - 若返回非 false,说明本进程是首个写入者 → 安全执行
mkdir($path, 0755, true) - 若失败,说明其他进程已抢先完成,直接继续业务逻辑
- 无需清理 flag 文件,它只起协调作用
生产环境建议:交由部署或存储层处理
高频动态目录创建(如按时间分片)本质是架构设计信号。更可持续的做法是:
- 预热生成常用目录(如每天凌晨生成当日
uploads/Y/m/d结构),避开请求高峰 - 使用对象存储(如 S3、MinIO),其 PUT 操作天然幂等,无需手动建目录
- 若必须用本地文件系统,将目录创建逻辑下沉到独立的队列服务或初始化脚本,PHP 请求只做“写入”,不负责“准备”
php免费学习视频:立即使用
踏上前端学习之旅,开启通往精通之路!从前端基础到项目实战,循序渐进,一步一个脚印,迈向巅峰!











