如何绕过 PHP 内存限制



所以,我们有一个有趣的场景。我们的客户有一个我们构建的自定义CMS。其中一个部分允许对记录进行批处理,因此它显示为表。每条记录都有几个文本字段和一个选择框。当数据库很小时,这工作得很好,但现在表中有超过 10,000 条记录,生成这些选择框正在扼杀我们的 PHP 内存限制。

我不是在寻找为我编写代码的人,而是给我一个更好的方法来做到这一点的想法。

以下是它的工作原理:

1) 进行数据库调用,生成选项列表以填充选择框。它从 PHP 返回到前端,作为一个名为$topping_images的巨大级联字符串变量,如下所示:

<option>Select an image</option><option value="cheese_mozz_ML.png">cheese_mozz_ML.png</option><option value="cheese_mozz_ML_HT.png">cheese_mozz_ML_HT.png</option><option value="cheese_mozz_ML_pan.png">cheese_mozz_ML_pan.png</option><option value="cheese_mozz_ML_SC.png">cheese_mozz_ML_SC.png</option>

2)进行数据库调用,返回要显示的记录;返回为名为$toppings的数组。

3) 生成我们的表,如下所示:

<? 
$x = 0;
foreach ($toppings as $topping) : ?>
<tr>
<td style="width:25%">
<input type="text" id="t_name<?= $x ?>" value="<?= $topping['topping_name'] ?>" />
</td>
<td style="width:29%">
<input type="hidden" id="topp_img_hdn<?= $x ?>" value="<?= $topping['image_path']; ?>" />
<select id="topp_img<?= $x ?>">
<?= $topping_images ?>
</select>
</td>
<? $x++; ?>
<? endforeach; ?>

我们不能更改我们的内存限制(通过 php.ini 或使用ini_set('memory_limit')),我们的客户绝对拒绝考虑分页以允许将较小的数据集发送到每个页面加载。

我是否错过了一种非常明显的方法来减少这里的处理时间和内存使用量?

是的。 停止使用 fetchAll()。 在任何给定时间,在内存中只保留 1 行,即您实际正在处理的行。

$rows=$db->query("SELECT * FROM tbl")->fetchAll(PDO::FETCH_ASSOC);
foreach($rows as $row){...}

如果有很多行,这可能会使用大量内存,因为它会同时将所有行保留在内存中。

现在,这个几乎等效的代码,

foreach($db->query("SELECT * FROM tbl",PDO::FETCH_ASSOC) as $row){...}

在任何给定时间只会在内存中保留 1 行,即您正在使用的行(垃圾收集器中的滞后除外,我猜测在任何给定时间最多 2 行)

我建议:

  • $topping_images作为数组返回到前端,而不是呈现的 HTML
  • 使用 select2 生成包含该数据的 JSON 化版本的选择框 (https://select2.github.io/options.html#data)。

因此,例如:

<? 
$x = 0;
foreach ($toppings as $topping) : ?>
<tr>
<td style="width:25%">
<input type="text" id="t_name<?= $x ?>" value="<?= $topping['topping_name'] ?>" />
</td>
<td style="width:29%">
<input type="hidden" id="topp_img_hdn<?= $x ?>" value="<?= $topping['image_path']; ?>" />
<select class="topping-images" id="topp_img<?= $x ?>"></select>
</td>
</tr>
<? $x++; ?>
<? endforeach; ?>
<script>
$(".topping-images").select2({
data: <?= json_encode($topping_images); ?>
});
</script>

使用此解决方案获得的另一个好处是,这个庞大的列表现在可以搜索。

编辑:你可以用普通的旧jQuery实现类似的效果

我还没有测试过这个,它绝对不是最优雅的解决方案,但它看起来应该可以工作。

<script>
var optionsArr = <?= json_encode($topping_images); ?>;
var options = optionsArr.map(function(topping){
return '<option value="' + topping.id + '">' + topping.name + '</option>';
}).join('');
$('.topping-images').html(options);
</script>

不确定您是否还需要解决方案,但我今天偶然发现了一种可能性......

问题很可能出在 foreach 循环中,因为它必须将整个数据集加载到内存中。 生成器允许您循环访问内存占用量小得多的数据集,因此可以缓解此问题。

最新更新