报错长这样:

PHP Fatal error:  Allowed memory size of 134217728 bytes exhausted
(tried to allocate 20480 bytes) in
/www/wwwroot/site/wp-content/plugins/xxx/class-export.php on line 412

很多人看到就把 memory_limit 从 128M 调到 512M,改完不报了,过两个月又报。因为那个数字不是问题本身,是症状。

这篇讲清楚:这个 128M 是谁设的、三种真实的超限场景、以及怎么定位到具体哪一行炸的。

一、134217728 是哪来的

134217728 / 1024 / 1024 = 128,就是 128 MB,PHP 的默认 memory_limit。

注意是单次请求的上限,不是整个 PHP 进程。一个请求处理完就释放了。

查当前值:

php -i | grep memory_limit
# 或者在站点根目录放个 phpinfo.php 看

三个地方都能设,优先级从低到高:

位置 作用范围 说明
php.ini 全局 宝塔在 /www/server/php/81/etc/php.ini
.user.ini 单个站点目录 宝塔会在网站根目录生成,改它比改 php.ini 安全
ini_set() 当前请求 插件里可能自己调过,会覆盖前两个

先把这三处的值都对一遍。经常出现的情况是:你在 php.ini 改成 256M,某个插件里一句 ini_set('memory_limit','128M') 又给压回去了。

二、三种真实成因

成因一:一次处理太多数据

典型是导出、备份、批量替换这类插件。它们会把整张表读进内存转成数组。

算一下就明白了:

数据量 PHP 数组开销 合计
1 万行 × 10 字段 约 12 MB 可接受
10 万行 × 10 字段 约 120 MB 刚好爆 128M
50 万行 约 600 MB 调多大都不够

PHP 的一个关联数组元素开销在 100 字节以上,比大多数人想的高得多。所以这类问题的正确解法不是调大内存,是分批处理:

for ($offset = 0; $offset < $total; $offset += 500) {
    $rows = $wpdb->get_results($wpdb->prepare(
        "SELECT * FROM wp_posts LIMIT %d OFFSET %d", 500, $offset
    ));
    // 处理完立即释放
    unset($rows);
}

成因二:处理大图

这个最容易被忽略,因为文件本身不大。

一张 4000×3000 的 JPEG,文件大小可能只有 2 MB。但 GD 库解码之后,每个像素占 4 字节(RGBA):

4000 × 3000 × 4 = 48,000,000 字节 ≈ 45.8 MB

一张就吃 46 MB。用户一次上传 5 张,WordPress 要生成多个尺寸的缩略图,同时解 2–3 张,128M 直接见底。

Imagick 稍好一点,但也差不太多。

所以症状很典型:只有上传大图时报错,平时都正常。

解法两个:

  • 调大 memory_limit 到 256M(治标,但对图片站合理)
  • 上传前限制尺寸,或者装个自动压缩插件(治本)

成因三:递归或者死循环

这种最值钱,因为调内存根本解决不了,调到 2G 照样爆。

常见写法:

function get_children($id) {
    $out = array();
    $items = get_terms(array('parent' => $id));
    foreach ($items as $it) {
        $out[] = $it;
        $out = array_merge($out, get_children($it->term_id)); // 没有终止条件
    }
    return $out;
}

分类有父子关系、菜单有层级,这种递归函数到处都有。一旦数据成环(A 的父级是 B,B 的父级又是 A),就是无限递归。

另一个常见来源是 WP_Query 嵌套:在 the_post() 循环里又开一个 WP_Query 却没 wp_reset_postdata(),查询对象越积越多。

三、怎么定位到具体哪一行

报错信息其实已经给了文件名和行号:

in /www/wwwroot/site/wp-content/plugins/xxx/class-export.php on line 412

关键是要确保 PHP 真的把错误写进日志。WordPress 默认会把错误吞掉,页面上只显示白屏。

在 wp-config.php 里临时打开:

define('WP_DEBUG', true);
define('WP_DEBUG_LOG', true);
define('WP_DEBUG_DISPLAY', false);
@ini_set('display_errors', 0);

WP_DEBUG_DISPLAY 必须是 false,否则错误会直接打在页面上,把布局打乱,还可能泄露路径。

日志在 wp-content/debug.log。

如果是偶发的、复现不了的,加一行打印调用栈:

register_shutdown_function(function () {
    $e = error_get_last();
    if ($e && strpos($e['message'], 'memory size') !== false) {
        error_log(print_r(debug_backtrace(DEBUG_BACKTRACE_IGNORE_ARGS), true));
    }
});

内存耗尽的 fatal error 是捕获不到普通异常的,只能挂在 shutdown 上。

四、该不该直接把 128M 调到 512M

大多数情况下不该。

调大只是把崩溃点往后推。原来 10 万行爆,调到 512M 变成 40 万行爆。等到数据涨到 40 万行,你还得再调一次,而那时候可能已经是半夜被客户电话叫醒。

合理的做法是分情况:

场景 怎么处理
图片站,用户上传大图 调到 256M,同时限制上传尺寸
导出/备份插件 改成命令行跑,CLI 下的 memory_limit 默认是 -1
递归导致 改代码,加终止条件
不明原因偶发 先开到 256M 止血,同时开日志抓现场

顺带说一句:命令行执行 PHP 时 memory_limit 默认是不限制的(-1)。所以同样的脚本,网页跑爆、命令行跑没事,这不是玄学。

五、和服务器 OOM 不是一回事

这两个经常被混为一谈:

PHP memory_limit 服务器 OOM Killer
触发者 PHP 自己 Linux 内核
表现 页面白屏 + 日志有 Fatal error 进程直接消失,日志里没有 PHP 错误
影响范围 单个请求 整个进程被 kill
查询方式 grep memory size dmesg | grep -i "killed process"

如果日志里只有 502、没有 PHP 错误,去查 dmesg,那是另一个问题。

看到 Allowed memory size 就调大内存,等于看到仪表盘亮红灯就把灯泡拧了。数字是结果,得先找到那个把它顶上去的东西。

俗人源码网免责声明:

1、本站所有资源均收集于互联网,分享目的仅限学习与参考。如有侵权,请联系 1685698671@qq.com 及时删除!

2、本站资源不保证完整性和安全性,下载后请自行检测。使用过程中出现的任何问题均与本站无关,敬请自行处理。

3、本站资源仅限个人学习研究使用,严禁用于任何违法违纪用途。因使用者违规使用产生的一切后果,由使用者自行承担,本站不承担任何责任。