Php 将表格数据导出到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-&

我正在从表数据生成CSV文件。表格记录以百万计。问题是,当我单击“导出到CSV”按钮时,生成CSV文件需要6分钟以上的时间,而我的服务器在6分钟后抛出超时错误

更新:我知道我可以增加超时时间,但我需要优化下面的脚本和查询

将用户导出到CSV的功能

 // 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。是的,我会给它一个机会。