php 8.5 彻底移除 session.gc_probability/session.gc_divisor 的概率触发机制,gc 改为仅在 session_start() 时按需延迟执行:需同时满足存在过期文件且距上次 gc 超过 session.gc_maxlifetime。

PHP 8.5 的 session.gc_probability 和 session.gc_divisor 不再控制实际触发概率
PHP 8.5 彻底移除了基于概率的 session 垃圾回收(GC)触发机制。以前靠 session.gc_probability/session.gc_divisor 控制“每多少次请求执行一次 GC”,现在这个逻辑被废弃了——无论你设成 1/100 还是 0/1,都不会影响 GC 是否运行。
原因很简单:PHP 内部改用「按需延迟触发」模型,GC 只在 session_start() 时,且满足两个条件才真正执行:一是当前 session.save_path 下存在过期文件(filemtime 超过 session.gc_maxlifetime),二是上一次 GC 时间距今已超 session.gc_maxlifetime(默认 1440 秒)。
- 旧配置仍可写,但完全被忽略;修改它们不会带来任何行为变化
-
session.gc_probability = 0不再能“禁用 GC”——它只是个无效开关 - 如果你依赖旧版概率触发做监控或日志埋点,这部分逻辑会失效
session.gc_maxlifetime 现在同时决定过期判断 + GC 触发间隔
在 PHP 8.5 中,session.gc_maxlifetime 的语义变重了:它既是 session 数据文件的“存活时长上限”,也是 GC 扫描周期的硬性间隔。也就是说,哪怕你每秒调用 100 次 session_start(),GC 最多每 session.gc_maxlifetime 秒执行一次,且只清理那些 filemtime 小于 time() - session.gc_maxlifetime 的文件。
- 若你把
session.gc_maxlifetime设为 300(5 分钟),GC 最快也得等满 5 分钟才可能再跑一次 - 该值必须与前端 cookie 的
Max-Age/Expires对齐,否则会出现“用户还在线,但服务端 session 已被删”的错位 - 使用 redis 或 memcached 存储 session 时,此配置仅影响 PHP 自己的文件扫描逻辑,后端存储的过期由自身 TTL 控制,两者需独立协调
自定义 session handler 必须显式实现 GC 逻辑
如果你用了 SessionHandlerInterface 实现自定义 session 处理器(比如对接数据库或对象存储),PHP 8.5 不再代你调用 gc() 方法——除非你在 session_start() 前手动触发,或者自己加定时任务。
立即学习“PHP免费学习笔记(深入)”;
旧代码里依赖“PHP 自动调用 gc()”来清理过期记录的,现在会持续累积脏数据。
- 检查你的
gc()方法是否还在被调用:加日志或断点,发现没执行就是被跳过了 - 推荐在应用启动时或低峰期主动调用一次
$handler->gc($maxlifetime) - 不要在
read()或write()里嵌套 GC 清理,容易引发并发写冲突或性能抖动
验证 GC 是否真的在工作:别信 phpinfo(),要看文件时间戳
phpinfo() 里显示的 session.gc_* 配置项还是老样子,但这只是残留字段,不能反映真实行为。要确认 GC 是否生效,得直接看 session.save_path 目录下的文件:
ls -lt /var/lib/php/sessions/ | head -10
观察 sess_* 文件的修改时间(mtime)。如果大量文件的 mtime 明显早于 time() - session.gc_maxlifetime 却没被删除,说明 GC 没触发或失败。
- 常见干扰项:目录权限不对(PHP 进程无法 unlink)、NFS 挂载导致
filemtime()不准、SELinux 限制 - 错误信息如
Warning: Unknown: Failed to write session data (files)可能掩盖 GC 失败,需查 error_log - 用
strace -e trace=unlink,stat64 -p $(pgrep php-fpm)可抓到实际的清理系统调用
GC 的边界其实很窄:它只管“删过期文件”,不负责锁清理、内存释放或外部存储同步。这些都得你自己兜底。