PHP 大量数据时脚本内部服务器错误
PHP Script Internal Server Error when lots of data
总结
这是一个脚本(CakePHP 2.10.18 - LAMP 专用服务器 PHP 5.3)从 2 MySQL tables,然后对数据做一些处理输出到excel.
Table 1 有 用户 ,Table 2 有关于这些用户的 信息(每个用户一条记录)。该脚本的目标是从 Table 1 中抓取用户的记录,从 Table 2 中抓取其相关信息,并将其放在 excel 行中(使用 PHPExcel_IOFactory 图书馆)。
从那些table中提取的信息是每个大约8000条记录,table本身分别有100K和300K的总记录。这些 table 中的所有字段都是 ints 和小的 varchars 除了第二个 table 中的一个字段(datos_progreso
在下面的代码中看到),它是一个文本字段并包含序列化数据,但没什么大不了的。
问题是,如果我 运行 完整 16000 条记录的脚本,我会得到一个 内部服务器错误 (在日志中没有任何解释),如果我 运行 1000 条记录的脚本一切正常,所以这似乎指出这是一个资源问题。
我试过(除其他外,我将在最后解释)将 memory_limit
从 128M 增加到 8GB(是的,你没有看错),max_execution_time
从 90 秒增加到 300 秒, max_input_vars
从 1000 到 10000,这并不能解决问题。
我的想法是,数据量并没有那么大,不会导致资源 运行 耗尽,但我已经尝试以多种方式优化脚本,但无法使其正常工作。我让它工作的唯一一次是 运行 像我上面提到的那样在一小部分记录上使用它。
我想知道是否可以通过脚本或 php-配置来解决此问题。顺便说一句,我无法用信息更改数据库tables。
代码
这只是我认为重要的相关代码位,脚本更长:
$this->Usuario->bindModel(
array('hasMany' => array(
'UsuarioProgreso' => array('className' => 'UsuarioProgreso', 'foreignKey' => 'id_usuario', 'conditions' => array('UsuarioProgreso.id_campania' => $id_campania)))
));
$usuarios = $this->Usuario->find('all', array(
'conditions'=>array('Usuario.id_campania'=>$id_campania, 'Usuario.fecha_registro >'=>'2020-05-28'),
'fields'=>array('Usuario.id_usuario', 'Usuario.login', 'Usuario.nombre', 'Usuario.apellido', 'Usuario.provincia', 'Usuario.telefono', 'Usuario.codigo_promocion'),
'order'=>array('Usuario.login ASC')
));
$usuario = null;
$progreso_usuario = null;
$datos_progreso = null;
$i = 2;
foreach ($usuarios as $usuario) {
if (isset($usuario['UsuarioProgreso']['datos_progreso'])) {
$datos_progreso = unserialize($progreso['UsuarioProgreso']['datos_progreso']);
$unit = 1;
$column = 'G';
while ($unit <= 60) {
if (isset($datos_progreso[$unit]['punt']))
$puntuacion = $datos_progreso[$unit]['punt'];
else
$puntuacion = ' ';
$objSheet->getCell($column.$i)->setValue($puntuacion);
$column++;
$unit++;
}
$nivel = 1;
$unidad_nivel = array(1 => 64, 2 => 68, 3 => 72, 4 => 76, 5 => 80, 6 => 84);
while ($nivel <= 6) {
$unidad = $unidad_nivel[$nivel];
if (isset($datos_progreso[$unidad]['punt']))
$puntuacion = $datos_progreso[$unidad]['punt'];
else
$puntuacion = ' ';
$objSheet->getCell($column.$i)->setValue($puntuacion);
$column++;
$nivel++;
}
}
//Free the variables
$usuario = null;
$progreso_usuario = null;
$datos_progreso = null;
$i++;
}
我试过的
我试过不使用 bindModel
,而是分别加载两个 table 的信息。所以首先加载用户的所有信息,循环遍历它,并在每个循环中从 Table 2.
中获取该特定用户的信息
我也尝试过与上述类似的方法,但不是一次为来自 Table 1 的用户加载所有信息,而是先加载他们所有的 ID,然后循环遍历这些 ID 以获取来自 Table 1 和 Table 2 的信息。我想这样我会使用更少的内存。
我也试过不使用 CakePHP 的 find()
,而是使用 fetchAll()
和 "manual" 查询,因为经过一些研究,它似乎在内存方面更有效(似乎没有什么不同)
如果我可以提供任何其他信息以帮助更好地理解正在发生的事情,请告诉我:)
编辑:
按照评论中的建议,我已经在 shell 脚本中实现了它并且工作正常(需要一段时间,但没有问题完成)。
话虽如此,我仍然希望通过 Web 界面完成这项工作。为了弄清楚发生了什么,并且由于 error_logs 并没有真正显示任何相关内容,我决定自己进行一些性能测试。
测试之后,这些是我的发现:
- 这不是内存问题,因为脚本最多使用大约 300 MB,我给它
memory_limit
8GB
- 无论是通过 web 调用还是 shell 脚本,内存使用情况都非常相似
- 这不是超时问题,因为我给脚本设置了 20 分钟的限制,但它在此之前就崩溃了
还有什么其他设置可以限制 this/running 在 shell 脚本时不会失败?
我解决这个问题的方法是按照评论中的建议使用 shell 脚本。我明白我最初打算的方法是不正确的,虽然我无法弄清楚到底是什么导致了错误,但很明显使用网络脚本是问题的根源。
总结
这是一个脚本(CakePHP 2.10.18 - LAMP 专用服务器 PHP 5.3)从 2 MySQL tables,然后对数据做一些处理输出到excel.
Table 1 有 用户 ,Table 2 有关于这些用户的 信息(每个用户一条记录)。该脚本的目标是从 Table 1 中抓取用户的记录,从 Table 2 中抓取其相关信息,并将其放在 excel 行中(使用 PHPExcel_IOFactory 图书馆)。
从那些table中提取的信息是每个大约8000条记录,table本身分别有100K和300K的总记录。这些 table 中的所有字段都是 ints 和小的 varchars 除了第二个 table 中的一个字段(datos_progreso
在下面的代码中看到),它是一个文本字段并包含序列化数据,但没什么大不了的。
问题是,如果我 运行 完整 16000 条记录的脚本,我会得到一个 内部服务器错误 (在日志中没有任何解释),如果我 运行 1000 条记录的脚本一切正常,所以这似乎指出这是一个资源问题。
我试过(除其他外,我将在最后解释)将 memory_limit
从 128M 增加到 8GB(是的,你没有看错),max_execution_time
从 90 秒增加到 300 秒, max_input_vars
从 1000 到 10000,这并不能解决问题。
我的想法是,数据量并没有那么大,不会导致资源 运行 耗尽,但我已经尝试以多种方式优化脚本,但无法使其正常工作。我让它工作的唯一一次是 运行 像我上面提到的那样在一小部分记录上使用它。
我想知道是否可以通过脚本或 php-配置来解决此问题。顺便说一句,我无法用信息更改数据库tables。
代码
这只是我认为重要的相关代码位,脚本更长:
$this->Usuario->bindModel(
array('hasMany' => array(
'UsuarioProgreso' => array('className' => 'UsuarioProgreso', 'foreignKey' => 'id_usuario', 'conditions' => array('UsuarioProgreso.id_campania' => $id_campania)))
));
$usuarios = $this->Usuario->find('all', array(
'conditions'=>array('Usuario.id_campania'=>$id_campania, 'Usuario.fecha_registro >'=>'2020-05-28'),
'fields'=>array('Usuario.id_usuario', 'Usuario.login', 'Usuario.nombre', 'Usuario.apellido', 'Usuario.provincia', 'Usuario.telefono', 'Usuario.codigo_promocion'),
'order'=>array('Usuario.login ASC')
));
$usuario = null;
$progreso_usuario = null;
$datos_progreso = null;
$i = 2;
foreach ($usuarios as $usuario) {
if (isset($usuario['UsuarioProgreso']['datos_progreso'])) {
$datos_progreso = unserialize($progreso['UsuarioProgreso']['datos_progreso']);
$unit = 1;
$column = 'G';
while ($unit <= 60) {
if (isset($datos_progreso[$unit]['punt']))
$puntuacion = $datos_progreso[$unit]['punt'];
else
$puntuacion = ' ';
$objSheet->getCell($column.$i)->setValue($puntuacion);
$column++;
$unit++;
}
$nivel = 1;
$unidad_nivel = array(1 => 64, 2 => 68, 3 => 72, 4 => 76, 5 => 80, 6 => 84);
while ($nivel <= 6) {
$unidad = $unidad_nivel[$nivel];
if (isset($datos_progreso[$unidad]['punt']))
$puntuacion = $datos_progreso[$unidad]['punt'];
else
$puntuacion = ' ';
$objSheet->getCell($column.$i)->setValue($puntuacion);
$column++;
$nivel++;
}
}
//Free the variables
$usuario = null;
$progreso_usuario = null;
$datos_progreso = null;
$i++;
}
我试过的
我试过不使用 bindModel
,而是分别加载两个 table 的信息。所以首先加载用户的所有信息,循环遍历它,并在每个循环中从 Table 2.
我也尝试过与上述类似的方法,但不是一次为来自 Table 1 的用户加载所有信息,而是先加载他们所有的 ID,然后循环遍历这些 ID 以获取来自 Table 1 和 Table 2 的信息。我想这样我会使用更少的内存。
我也试过不使用 CakePHP 的 find()
,而是使用 fetchAll()
和 "manual" 查询,因为经过一些研究,它似乎在内存方面更有效(似乎没有什么不同)
如果我可以提供任何其他信息以帮助更好地理解正在发生的事情,请告诉我:)
编辑:
按照评论中的建议,我已经在 shell 脚本中实现了它并且工作正常(需要一段时间,但没有问题完成)。
话虽如此,我仍然希望通过 Web 界面完成这项工作。为了弄清楚发生了什么,并且由于 error_logs 并没有真正显示任何相关内容,我决定自己进行一些性能测试。
测试之后,这些是我的发现:
- 这不是内存问题,因为脚本最多使用大约 300 MB,我给它
memory_limit
8GB - 无论是通过 web 调用还是 shell 脚本,内存使用情况都非常相似
- 这不是超时问题,因为我给脚本设置了 20 分钟的限制,但它在此之前就崩溃了
还有什么其他设置可以限制 this/running 在 shell 脚本时不会失败?
我解决这个问题的方法是按照评论中的建议使用 shell 脚本。我明白我最初打算的方法是不正确的,虽然我无法弄清楚到底是什么导致了错误,但很明显使用网络脚本是问题的根源。