报错长这样:
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、本站资源仅限个人学习研究使用,严禁用于任何违法违纪用途。因使用者违规使用产生的一切后果,由使用者自行承担,本站不承担任何责任。


评论(0)