Php 将表格数据导出到CSV需要花费很长时间
我正在从表数据生成CSV文件。表格记录以百万计。问题是,当我单击“导出到CSV”按钮时,生成CSV文件需要6分钟以上的时间,而我的服务器在6分钟后抛出超时错误 更新:我知道我可以增加超时时间,但我需要优化下面的脚本和查询强> 将用户导出到CSV的功能Php 将表格数据导出到CSV需要花费很长时间,php,symfony,csv,query-optimization,symfony1,Php,Symfony,Csv,Query Optimization,Symfony1,我正在从表数据生成CSV文件。表格记录以百万计。问题是,当我单击“导出到CSV”按钮时,生成CSV文件需要6分钟以上的时间,而我的服务器在6分钟后抛出超时错误 更新:我知道我可以增加超时时间,但我需要优化下面的脚本和查询 将用户导出到CSV的功能 // Function for exporting all Quiz Users Leads to CSV public function executeUserExportLeads($request) { $this-&
// Function for exporting all Quiz Users Leads to CSV
public function executeUserExportLeads($request) {
$this->export = true;
$this->pager = new myPropelPager('BtqUser', 9999999);
$this->pager->setCriteria(btqAdminBtqUserPeer::getBtqUsersForExport());
$this->pager->setPeerMethod('doSelectStmt');
$this->pager->setAction('newadmin/users');
$this->pager->setPage($request->getParameter('page', 1));
$this->pager->init();
//Generating CSV in server and giving user the CSV file for download
if($this->pager->getResults() > 0){
$filename = "userleads".".csv";
unlink($filename);
$uploaddir = sfConfig::get('sf_upload_dir');
$path = $uploaddir ."/" . $filename;
fopen($path , 'a');
$handle = fopen($path, 'w+');
//set column headers
$fields = array('Date', 'Name', 'Email', 'Lead From', 'State', 'Phone No',"\r\n");
fwrite($handle, implode(',',$fields));
//output each row of the data, format line as csv and write to file pointer
foreach($this->pager->getResults() as $row){
$lineData = array(date('M-d-Y', strtotime($row['schedule_date'])), $row['name'], $row['email'] , $row['lead_from'], $row['state'], $row['telefon'],"\r\n");
fwrite($handle, implode(',',$lineData));
}
fclose($handle);
$result_array = array('fileURL' => 'http://this-site.com/uploads/'.$filename);
return $this->renderText(json_encode($result_array));
}
exit;
}
查询导出(这将获取所有用户记录以导出为csv):
public static function getBtqUsersForExport() {
$criteria = new Criteria();
$criteria->clearSelectColumns();
$criteria->addSelectColumn("btq_user.id as id");
$criteria->addSelectColumn("btq_user.name as name");
$criteria->addSelectColumn("btq_user.email as email");
$criteria->addSelectColumn("btq_user.lead_from as lead_from");
$criteria->addSelectColumn("btq_user.telefon as telefon");
$criteria->addSelectColumn("btq_user.datain as datain");
$criteria->addSelectColumn("state.state as state");
$criteria->addSelectColumn("lead_schedule.id as schedule_id");
$criteria->addSelectColumn("lead_schedule.created_at as schedule_date");
$criteria->addJoin(self::STATE_ID, StatePeer::ID, Criteria::LEFT_JOIN);
$criteria->addJoin(self::ID, LeadSchedulePeer::LEAD_ID, Criteria::LEFT_JOIN);
$criteria->addJoin(self::ID, BtqUserTrackBlogVideoPeer::USER_ID, Criteria::LEFT_JOIN);
$criteria->addGroupByColumn(self::EMAIL);
$criteria->add(BtqUserPeer::IS_DUMMY_DETAIL, "1", Criteria::NOT_EQUAL);
$criteria->addDescendingOrderByColumn(self::DATAIN);
return $criteria;
}
<script>
function move() {
var hidden = document.getElementById("myProgress");
hidden.classList.remove("hidden");
var elem = document.getElementById("myBar");
var width = 1;
var id = setInterval(frame, 5000);
function frame() {
if (width >= 100) {
clearInterval(id);
var hidden = document.getElementById("myProgress");
hidden.classList.add("hidden");
} else {
if(width>100){
}else{
width++;
elem.style.width = width + '%';
}
}
}
$('#exportCSV').submit(function(event){
event.preventDefault();
});
$.ajax({
data: {export: "Export To CSV"},
type: 'POST',
url: 'userExportLeads',
success: function(result){
console.log(result);
var data = JSON.parse(result);
clearInterval(id);
$('#myBar').css('width','100%');
$('#myProgress').delay(5000).fadeOut();
location.href = data.fileURL;
}
});
}
</script>
<form id="exportCSV" action="<?php echo url_for('newadmin/userExportLeads'); ?>" method="POST">
<input type="submit" onclick="move()" name="export" value="Export To CSV" />
</form>
</div>
<br/>
<div id="myProgress" class="hidden" align="left">
<div id="myBar"></div>
</div>"
请求的Ajax:
public static function getBtqUsersForExport() {
$criteria = new Criteria();
$criteria->clearSelectColumns();
$criteria->addSelectColumn("btq_user.id as id");
$criteria->addSelectColumn("btq_user.name as name");
$criteria->addSelectColumn("btq_user.email as email");
$criteria->addSelectColumn("btq_user.lead_from as lead_from");
$criteria->addSelectColumn("btq_user.telefon as telefon");
$criteria->addSelectColumn("btq_user.datain as datain");
$criteria->addSelectColumn("state.state as state");
$criteria->addSelectColumn("lead_schedule.id as schedule_id");
$criteria->addSelectColumn("lead_schedule.created_at as schedule_date");
$criteria->addJoin(self::STATE_ID, StatePeer::ID, Criteria::LEFT_JOIN);
$criteria->addJoin(self::ID, LeadSchedulePeer::LEAD_ID, Criteria::LEFT_JOIN);
$criteria->addJoin(self::ID, BtqUserTrackBlogVideoPeer::USER_ID, Criteria::LEFT_JOIN);
$criteria->addGroupByColumn(self::EMAIL);
$criteria->add(BtqUserPeer::IS_DUMMY_DETAIL, "1", Criteria::NOT_EQUAL);
$criteria->addDescendingOrderByColumn(self::DATAIN);
return $criteria;
}
<script>
function move() {
var hidden = document.getElementById("myProgress");
hidden.classList.remove("hidden");
var elem = document.getElementById("myBar");
var width = 1;
var id = setInterval(frame, 5000);
function frame() {
if (width >= 100) {
clearInterval(id);
var hidden = document.getElementById("myProgress");
hidden.classList.add("hidden");
} else {
if(width>100){
}else{
width++;
elem.style.width = width + '%';
}
}
}
$('#exportCSV').submit(function(event){
event.preventDefault();
});
$.ajax({
data: {export: "Export To CSV"},
type: 'POST',
url: 'userExportLeads',
success: function(result){
console.log(result);
var data = JSON.parse(result);
clearInterval(id);
$('#myBar').css('width','100%');
$('#myProgress').delay(5000).fadeOut();
location.href = data.fileURL;
}
});
}
</script>
<form id="exportCSV" action="<?php echo url_for('newadmin/userExportLeads'); ?>" method="POST">
<input type="submit" onclick="move()" name="export" value="Export To CSV" />
</form>
</div>
<br/>
<div id="myProgress" class="hidden" align="left">
<div id="myBar"></div>
</div>"
函数move(){
var hidden=document.getElementById(“myProgress”);
hidden.classList.remove(“隐藏”);
var elem=document.getElementById(“myBar”);
var宽度=1;
var id=设置间隔(帧,5000);
函数框架(){
如果(宽度>=100){
清除间隔(id);
var hidden=document.getElementById(“myProgress”);
hidden.classList.add(“hidden”);
}否则{
如果(宽度>100){
}否则{
宽度++;
elem.style.width=宽度+'%';
}
}
}
$('#exportCSV')。提交(函数(事件){
event.preventDefault();
});
$.ajax({
数据:{导出:“导出到CSV”},
键入:“POST”,
url:'userExportLeads',
成功:功能(结果){
控制台日志(结果);
var data=JSON.parse(结果);
清除间隔(id);
$('#myBar').css('width','100%);
$(“#myProgress”).delay(5000).fadeOut();
location.href=data.fileURL;
}
});
}
以下是表单代码:
public static function getBtqUsersForExport() {
$criteria = new Criteria();
$criteria->clearSelectColumns();
$criteria->addSelectColumn("btq_user.id as id");
$criteria->addSelectColumn("btq_user.name as name");
$criteria->addSelectColumn("btq_user.email as email");
$criteria->addSelectColumn("btq_user.lead_from as lead_from");
$criteria->addSelectColumn("btq_user.telefon as telefon");
$criteria->addSelectColumn("btq_user.datain as datain");
$criteria->addSelectColumn("state.state as state");
$criteria->addSelectColumn("lead_schedule.id as schedule_id");
$criteria->addSelectColumn("lead_schedule.created_at as schedule_date");
$criteria->addJoin(self::STATE_ID, StatePeer::ID, Criteria::LEFT_JOIN);
$criteria->addJoin(self::ID, LeadSchedulePeer::LEAD_ID, Criteria::LEFT_JOIN);
$criteria->addJoin(self::ID, BtqUserTrackBlogVideoPeer::USER_ID, Criteria::LEFT_JOIN);
$criteria->addGroupByColumn(self::EMAIL);
$criteria->add(BtqUserPeer::IS_DUMMY_DETAIL, "1", Criteria::NOT_EQUAL);
$criteria->addDescendingOrderByColumn(self::DATAIN);
return $criteria;
}
<script>
function move() {
var hidden = document.getElementById("myProgress");
hidden.classList.remove("hidden");
var elem = document.getElementById("myBar");
var width = 1;
var id = setInterval(frame, 5000);
function frame() {
if (width >= 100) {
clearInterval(id);
var hidden = document.getElementById("myProgress");
hidden.classList.add("hidden");
} else {
if(width>100){
}else{
width++;
elem.style.width = width + '%';
}
}
}
$('#exportCSV').submit(function(event){
event.preventDefault();
});
$.ajax({
data: {export: "Export To CSV"},
type: 'POST',
url: 'userExportLeads',
success: function(result){
console.log(result);
var data = JSON.parse(result);
clearInterval(id);
$('#myBar').css('width','100%');
$('#myProgress').delay(5000).fadeOut();
location.href = data.fileURL;
}
});
}
</script>
<form id="exportCSV" action="<?php echo url_for('newadmin/userExportLeads'); ?>" method="POST">
<input type="submit" onclick="move()" name="export" value="Export To CSV" />
</form>
</div>
<br/>
<div id="myProgress" class="hidden" align="left">
<div id="myBar"></div>
</div>"
我不确定这是否是主要问题,是否会影响导出执行时间,但最好重构它
if ($this->pager->getResults() > 0) {
foreach ($this->pager->getResults() as $row) {
}
}
对此
$result = $this->pager->getResults();
if ($result.count() > 0) { //I'm not sure how myPropelPager works and what is the type of $result. Let's hope it is Countable
foreach ($result as $row) {
}
}
我也不知道$this->pager->getResults()
做什么。在最坏的情况下,调用它两次,执行DB查询两次。
我还希望$result
是一个游标,而不是包含所有结果的数组
我的优化可能有助于减少时间和内存使用,但我不确定。
无论如何,在所有优化之后,如果您要导出数百万行,您将再次遇到此问题。最佳做法是将此过程与web服务器上下文分离,并在后台使用Gearman之类的工具进行操作。我不确定这是否是主要问题,是否会影响导出执行时间,但最好对其进行重构
if ($this->pager->getResults() > 0) {
foreach ($this->pager->getResults() as $row) {
}
}
对此
$result = $this->pager->getResults();
if ($result.count() > 0) { //I'm not sure how myPropelPager works and what is the type of $result. Let's hope it is Countable
foreach ($result as $row) {
}
}
我也不知道$this->pager->getResults()
做什么。在最坏的情况下,调用它两次,执行DB查询两次。
我还希望$result
是一个游标,而不是包含所有结果的数组
我的优化可能有助于减少时间和内存使用,但我不确定。
无论如何,在所有优化之后,如果您要导出数百万行,您将再次遇到此问题。最佳实践是将此过程与web服务器上下文分离,并在后台使用Gearman之类的工具进行操作。我同意评论中的建议,即切换到原始SQL可能是最佳选择
即使您没有这样做,也要弄清楚SQL推进运行的是什么,您可能可以进行一些优化。在doctrine中,您可以使用echo$query->getSqlQuery()
;我不熟悉如何在推进中做这件事。您还可以在这个慢速查询运行时连接到MySQL,SHOW FULL PROCESSLIST
应该会显示正在运行的查询
在该查询前面加上EXPLAIN
,您就可以看到MySQL的查询计划是如何实现的<代码>解释选择
如果您以前没有使用过它,理解起来就不是很直观,但是网上有很好的指南。我同意评论中的建议,即切换到原始SQL可能是最好的选择
即使您没有这样做,也要弄清楚SQL推进运行的是什么,您可能可以进行一些优化。在doctrine中,您可以使用echo$query->getSqlQuery()
;我不熟悉如何在推进中做这件事。您还可以在这个慢速查询运行时连接到MySQL,SHOW FULL PROCESSLIST
应该会显示正在运行的查询
在该查询前面加上
EXPLAIN
,您就可以看到MySQL的查询计划是如何实现的<代码>解释选择如果您以前没有使用过它,那么理解起来就不是很直观,但是网上有很好的指南。您有没有想过在这种情况下绕过ORM?大多数情况下,直接使用好的老PDO处理如此多的记录时,可能会很方便。@dlondero谢谢,这看起来是个不错的选择。你的建议是我应该使用原始sql。是的,我会给它一个机会。你有没有想过在这种情况下绕过ORM?大多数情况下,直接使用好的老PDO处理如此多的记录时,可能会很方便。@dlondero谢谢,这看起来是个不错的选择。你的建议是我应该使用原始sql。是的,我会给它一个机会。