PHP内存溢出报“Allowed memory size exhausted”是PHP进程超memory_limit,Redis OOM报“command not allowed when used memory > 'maxmemory'”是Redis超maxmemory;二者常共存,需分别查php.ini配置与redis-cli info memory、淘汰策略及大Key。

ThinkPHP 项目里 Redis 报 OOM,不是 Redis 自己炸了,而是 PHP 进程在操作 Redis 时内存耗尽——常见于导出、缓存预热、大列表查询等场景。必须同时查 PHP 内存限制和 Redis 配置,漏掉任一端都会白忙。
确认是 PHP 内存溢出还是 Redis 内存溢出
先看错误日志里到底是谁报的错:
- 如果报
Fatal error: Allowed memory size of XXX bytes exhausted→ 是 PHP 进程内存超限,跟memory_limit有关,和 Redis 的maxmemory无关 - 如果报
OOM command not allowed when used memory > 'maxmemory'→ 是 Redis 自身内存超限,需查redis-cli info memory和淘汰策略 - 两者可能共存:PHP 拿着 Redis 返回的几万条数据往数组里塞,
memory_limit先崩;而 Redis 里又存着没设过期时间的缓存,used_memory持续涨到触发maxmemory
PHP 层:ThinkPHP 启动阶段就吃内存,必须掐在源头
ThinkPHP 加载机制重,尤其开启调试或大量关联查询时,内存峰值很容易翻倍。不能只靠 ini_set('memory_limit', '512M') 临时顶包。
- 必须修改真实生效的
php.ini:运行php --ini(CLI)或输出phpinfo()(Web)确认Loaded Configuration File路径,改里面的memory_limit = 256M - ThinkPHP 生产环境务必关闭
app_debug:它会记录 SQL 日志、模板编译缓存、错误堆栈,实测可省 30%+ 峰值内存 - 避免在循环里反复调用
Cache::get()或Redis::get():改用Redis::mGet($keys)批量取;大结果集不用select()全捞,改用chunk(500, function($items) { ... }) - 手动释放资源:执行完
Redis::hGetAll('big_hash')后立刻unset($result),PHP 7.4+ 引用计数虽优化,但 TP 封装层不自动清空变量
Redis 层:别让 ThinkPHP 写进去的数据“赖着不走”
ThinkPHP 默认缓存驱动若直连 Redis,且没统一加过期时间,很容易写一堆永不过期的 key,几天后 used_memory 涨满。
立即学习“PHP免费学习笔记(深入)”;
- 检查当前 Redis 内存水位:
redis-cli info memory | grep -E "used_memory|maxmemory|mem_fragmentation_ratio";若mem_fragmentation_ratio > 1.5,说明碎片高,可执行redis-cli memory purge(Redis 4.0+) - 确认淘汰策略是否生效:
redis-cli config get maxmemory-policy;ThinkPHP 缓存场景推荐allkeys-lru,避免因部分 key 没设expire就卡死 - 查大 key:
redis-cli --bigkeys;尤其注意 ThinkPHP 自动生成的缓存 key,如think_cache:route:或think_cache:config:,它们可能是哈希结构但没拆分,单个占几十 MB - 业务代码里所有
Cache::set()必须显式传$ttl:哪怕只是3600,也比不设强;禁用Cache::forever(),上线前全局 grep 一遍
组合排查:ThinkPHP + Redis 的典型泄漏点
最隐蔽的问题往往出在“PHP 和 Redis 协同失能”的地方,比如导出功能里一边从数据库查,一边往 Redis 存中间结果,两边都不清理。
- 导出 Excel 时,别把全部数据塞进一个 Redis list:改用分页查库 + 分批
RPUSH到带序号的 key(如export:123:part_1),处理完立刻DEL对应 key - 静态变量缓存陷阱:自定义命令类里写
private static $cache = [],每次php think export:run都往里塞数据,内存只增不减 - Redis 连接未复用:TP 默认每次请求新建 Redis 实例,高并发下连接对象堆积;确认
config/cache.php中redis驱动已启用连接池(依赖 predis 或 phpredis 的持久连接支持) - 监控要落地:在关键流程前后打点,例如
echo "before redis get: " . memory_get_peak_usage() . "\n",比猜更准
真正难搞的从来不是单点配置,而是 PHP 请求生命周期里 Redis 数据写入、读取、释放的节奏没对齐——比如 ThinkPHP 的 Cache 门面类在请求结束时不会自动清空 Redis 中的内容,得靠你写清楚“什么时候该删”。



















