非常慢的循环PHP

Posted

技术标签:

【中文标题】非常慢的循环PHP【英文标题】:Very slow loop PHP 【发布时间】:2022-01-22 09:02:18 【问题描述】:

我是一名初级开发人员,我遇到了脚本循环问题。 循环循环大数组来自数据库。 问题是在最短的时间内完成循环是可能的,但目前,大约 500 个元素需要 15 分钟才能完成。 这是不可接受的。 我的文件类型需要引号中的空格。 这是私有类函数中的代码:

$length = count($this->fileH1);
for ($z = 0; $z < $length; $z++) 
    $this->fileH2[$z]['id_paziente'] = $this->fileH1[$z]->id_paziente;
    $this->fileH2[$z]['regione'] = decifra($_SESSION['cod_regione']);
    $this->fileH2[$z]['asl'] = decifra($_SESSION['cod_asl']);
    $this->fileH2[$z]['cod_struttura'] = decifra($_SESSION['cod_struttura']);
    $this->fileH2[$z]['tipo_assist'] = "RIA";
    $this->fileH2[$z]['tipo_strutt'] = decifra($_SESSION['tipo_struttura']);


    // CERCO LE INFO DELLE MENOMAZIONI DEL PAZIENTE
    $stmt_get_info_menomazioni = $this->centro->prepare('SELECT codice, icd9_nuovo FROM tbl_pazienti_terapie_menomazioni WHERE id_paziente = ? AND id_contratto = ? LIMIT 1');
    $stmt_get_info_menomazioni->bind_param("ii", $this->fileH1[$z]->id_paziente, $this->fileH1[$z]->id_contratto); //$fileH1[$z]['id_terapia']);
    $stmt_get_info_menomazioni->execute();
    $stmt_get_info_menomazioni->store_result();
    $stmt_get_info_menomazioni->bind_result($cod_menomazione, $icd9_menomazione);

    if ($stmt_get_info_menomazioni->num_rows > 0) 
        $stmt_get_info_menomazioni->fetch();

        if ($cod_menomazione !== NULL || $cod_menomazione !== '')
            $this->fileH2[$z]['cod_menomazione'] = $cod_menomazione;
        else $this->fileH2[$z]['cod_menomazione'] = '     ';

        if ($icd9_menomazione !== NULL || $icd9_menomazione !== '')
            $this->fileH2[$z]['icd9_menomazione'] = str_pad($icd9_menomazione, 10, " ");
        else $this->fileH2[$z]['icd9_menomazione'] = '          ';

     else 
        $this->fileH2[$z]['cod_menomazione'] = '     ';
        $this->fileH2[$z]['icd9_menomazione'] = '          ';
    
    $stmt_get_info_menomazioni->close();


    $this->fileH2[$z]['num_registro'] = $this->fileH1[$z]->anno_rif . decifra($_SESSION['cod_asl']) . '0' . date('y') . str_pad($_POST['mese'], 2, '0', STR_PAD_LEFT) . '0001';;      // "203021040001";      // AGGIUNGERE numero registro struttura
    $this->fileH2[$z]['medico_autorizz'] = '                ';
    $this->fileH2[$z]['cod_medico_autorizz'] = '       ';
    $this->fileH2[$z]['istat_primo_ricovero'] = '000000';
    $this->fileH2[$z]['progressivo'] = $this->fileH1[$z]->progressivo;

    // CERCO LE INFO DELLE TERAPIE DEL PAZIENTE
    $stmt_get_info_menomazioni = $this->centro->prepare('SELECT data_autorizz, data_inizio, data_fine FROM tbl_pazienti_contratti WHERE id_paziente = ? AND id = ? LIMIT 1');
    $stmt_get_info_menomazioni->bind_param("ii", $this->fileH1[$z]->id_paziente, $this->fileH1[$z]->id_contratto); //$fileH1[$z]['id_terapia']);
    $stmt_get_info_menomazioni->execute();
    $stmt_get_info_menomazioni->store_result();
    $stmt_get_info_menomazioni->bind_result($data_autorizz, $data_inizio, $data_fine);
    if ($stmt_get_info_menomazioni->num_rows > 0) 
        $stmt_get_info_menomazioni->fetch();
        if ($data_autorizz !== NULL || $data_autorizz !== '0000-00-00') $this->fileH2[$z]['data_prescrizione'] = date('dmY', strtotime($data_autorizz)); else        $this->fileH2[$z]['data_prescrizione'] = '        ';
        if ($data_inizio !== NULL || $data_inizio !== '0000-00-00') $this->fileH2[$z]['data_inizio_terapia'] = date('dmY', strtotime($data_inizio)); else        $this->fileH2[$z]['data_inizio_terapia'] = '        ';
        if ($data_fine !== NULL || $data_fine !== '0000-00-00') $this->fileH2[$z]['data_fine_terapia'] = date('dmY', strtotime($data_fine)); else        $this->fileH2[$z]['data_fine_terapia'] = '        ';


        // CALCOLO LE DATE DEL CICLO DI FATTURAZIONE
        $data_inizio_mese_attuale = date('Y-m-1');
        $data_inizio_terapia = date('Y-m-d', strtotime($data_inizio));
        if ($data_inizio_mese_attuale < $data_inizio_terapia)
            $this->fileH2[$z]['data_inizio_periodo_fatturazione'] = date('dmY', strtotime($data_inizio_terapia));
        else $this->fileH2[$z]['data_inizio_periodo_fatturazione'] = date('dmY', strtotime($data_inizio_mese_attuale));

        $data_fine_mese_attuale = date('Y-m-t');  // t = ultimo gg del mese attuale
        $data_fine_terapia = date('Y-m-d', strtotime($data_fine));
        if ($data_fine_mese_attuale > $data_fine_terapia)
            $this->fileH2[$z]['data_fine_periodo_fatturazione'] = date('dmY', strtotime($data_fine_terapia));
        else $this->fileH2[$z]['data_fine_periodo_fatturazione'] = date('dmY', strtotime($data_fine_mese_attuale));


        // CALCOLO QTA PRESTAZIONI EFFETTUATE
        $stmt_get_qta_prestaz_eff = $this->centro->prepare('SELECT COUNT(id) FROM tbl_pazienti_terapie_presenze WHERE MONTH(DATE(ingresso_effettuato)) = ? AND id_paziente = ?');
        $stmt_get_qta_prestaz_eff->bind_param('ii', $this->mese, $this->fileH1[$z]->id_paziente);
        $stmt_get_qta_prestaz_eff->execute();
        $stmt_get_qta_prestaz_eff->store_result();
        $stmt_get_qta_prestaz_eff->bind_result($qta_prestaz);
        $stmt_get_qta_prestaz_eff->fetch();

        $this->fileH2[$z]['qta_prestaz'] = str_pad($qta_prestaz, 3, "0", STR_PAD_LEFT);


        $this->fileH2[$z]['codifica_nomencl'] = 't';

        if ($this->fileH2[$z]['progressivo'] == '99')
            $this->fileH2[$z]['codice_prestaz'] = '       ';
        else $this->fileH2[$z]['codice_prestaz'] = '001.001';

        $this->fileH2[$z]['esenzione_1'] = '0';
        $this->fileH2[$z]['esenzione_2'] = '      ';
        $this->fileH2[$z]['esenzione_3'] = '0';

        $this->fileH2[$z]['onere'] = "1";
        $this->fileH2[$z]['importo_compart'] = '000000,00';
        $this->fileH2[$z]['posizione_compart'] = '0';

        if ($this->fileH1[$z]->tariffa !== NULL || $this->fileH1[$z]->tariffa !== '')
            $this->fileH2[$z]['importo_totale'] = str_replace('.', ',', str_pad(floatval($this->fileH1[$z]->tariffa) * $qta_prestaz, 9, "0", STR_PAD_LEFT));
        else $this->fileH2[$z]['importo_totale'] = "         ";

        $stmt_get_qta_prestaz_eff->close();

    
    $stmt_get_info_menomazioni->close();

    $this->fileH2[$z]['posizione_contab'] = ' ';
    $this->fileH2[$z]['err01'] = ' ';
    $this->fileH2[$z]['err02'] = ' ';
    $this->fileH2[$z]['err03'] = ' ';
    $this->fileH2[$z]['err04'] = ' ';
    $this->fileH2[$z]['err05'] = ' ';
    $this->fileH2[$z]['err06'] = ' ';
    $this->fileH2[$z]['err07'] = ' ';
    $this->fileH2[$z]['err08'] = ' ';
    $this->fileH2[$z]['err09'] = ' ';
    $this->fileH2[$z]['err10'] = ' ';

    $this->fileH2[$z]['anno_rif'] = $this->fileH1[$z]->anno_rif;
    $this->fileH2[$z]['cod_strut_erog'] = decifra($_SESSION['cod_struttura_eroga']);
    $this->fileH2[$z]['identificativo_mensile'] = $this->fileH1[$z]->identificativo_mensile;

    $this->fileH2[$z]['anno_mese_invio'] = date('Ym');
    $this->fileH2[$z]['asl_addebito'] = 000;

有人可以帮助我吗?

更新 1: 首先感谢大家的回答。 使用 JOIN 进行唯一调用,然后将准备语句移出循环。现在大约 500 个项目的时间是 5 分钟。

$sql = 'SELECT COUNT(tp.id),pc.data_autorizz, pc.data_inizio, pc.data_fine, tm.codice, tm.icd9_nuovo FROM tbl_pazienti_terapie_presenze as tp LEFT JOIN tbl_pazienti_contratti as pc ON tp.id_paziente = pc.id_paziente LEFT JOIN tbl_pazienti_terapie_menomazioni as tm ON tm.id_contratto = pc.id AND tm.id_paziente = pc.id_paziente WHERE MONTH(DATE(tp.ingresso_effettuato)) = ? AND tp.id_paziente = ? AND pc.id = ?';
    $do_sql = $this->centro->prepare($sql);
    $length = count($this->fileH1);
    for ($z = 0; $z < $length; $z++) 
        $this->fileH2[$z]['id_paziente'] = $this->fileH1[$z]['id_paziente'];['id_terapia'];
        $this->fileH2[$z]['regione'] = decifra($_SESSION['cod_regione']);
        $this->fileH2[$z]['asl'] = decifra($_SESSION['cod_asl']);
        $this->fileH2[$z]['cod_struttura'] = decifra($_SESSION['cod_struttura']);
        $this->fileH2[$z]['tipo_assist'] = "RIA";
        $this->fileH2[$z]['tipo_strutt'] = decifra($_SESSION['tipo_struttura']);

        $do_sql->bind_param('iii', $this->mese, $this->fileH1[$z]['id_paziente'], $this->fileH1[$z]['id_contratto']);
        $do_sql->execute();
        $do_sql->store_result();
        $do_sql->bind_result($qta_prestaz, $data_autorizz, $data_inizio, $data_fine, $cod_menomazione, $icd9_menomazione);
        if ($do_sql->num_rows > 0) 
            $do_sql->fetch();

            if ($cod_menomazione !== NULL && $cod_menomazione !== '')
                $this->fileH2[$z]['cod_menomazione'] = $cod_menomazione;
            else $this->fileH2[$z]['cod_menomazione'] = '     ';

            if ($icd9_menomazione !== NULL && $icd9_menomazione !== '')
                $this->fileH2[$z]['icd9_menomazione'] = str_pad($icd9_menomazione, 10, " ");
            else $this->fileH2[$z]['icd9_menomazione'] = '          ';

            if ($data_autorizz !== NULL && $data_autorizz !== '0000-00-00') $this->fileH2[$z]['data_prescrizione'] = date('dmY', strtotime($data_autorizz)); else        $this->fileH2[$z]['data_prescrizione'] = '        ';
            if ($data_inizio !== NULL && $data_inizio !== '0000-00-00') $this->fileH2[$z]['data_inizio_terapia'] = date('dmY', strtotime($data_inizio)); else        $this->fileH2[$z]['data_inizio_terapia'] = '        ';
            if ($data_fine !== NULL && $data_fine !== '0000-00-00') $this->fileH2[$z]['data_fine_terapia'] = date('dmY', strtotime($data_fine)); else        $this->fileH2[$z]['data_fine_terapia'] = '        ';


            if ($this->fileH1[$z]['tariffa'] !== NULL || $this->fileH1[$z]['tariffa'] !== '')
                $this->fileH2[$z]['importo_totale'] = str_replace('.', ',', str_pad(floatval($this->fileH1[$z]->tariffa) * $qta_prestaz, 9, "0", STR_PAD_LEFT));
            else $this->fileH2[$z]['importo_totale'] = '         ';

            $this->fileH2[$z]['codifica_nomencl'] = 't';

            if ($this->fileH2[$z]['progressivo'] == '99')
                $this->fileH2[$z]['codice_prestaz'] = '       ';
            else $this->fileH2[$z]['codice_prestaz'] = '001.001';

            $this->fileH2[$z]['esenzione_1'] = '0';
            $this->fileH2[$z]['esenzione_2'] = '      ';
            $this->fileH2[$z]['esenzione_3'] = '0';

            $this->fileH2[$z]['onere'] = "1";
            $this->fileH2[$z]['importo_compart'] = '000000,00';
            $this->fileH2[$z]['posizione_compart'] = '0';

            // CALCOLO LE DATE DEL CICLO DI FATTURAZIONE
            $data_inizio_mese_attuale = date('Y-m-1');
            $data_inizio_terapia = date('Y-m-d', strtotime($data_inizio));
            if ($data_inizio_mese_attuale < $data_inizio_terapia)
                $this->fileH2[$z]['data_inizio_periodo_fatturazione'] = date('dmY', strtotime($data_inizio_terapia));
            else $this->fileH2[$z]['data_inizio_periodo_fatturazione'] = date('dmY', strtotime($data_inizio_mese_attuale));

            $data_fine_mese_attuale = date('Y-m-t');  // t = ultimo gg del mese attuale
            $data_fine_terapia = date('Y-m-d', strtotime($data_fine));
            if ($data_fine_mese_attuale > $data_fine_terapia)
                $this->fileH2[$z]['data_fine_periodo_fatturazione'] = date('dmY', strtotime($data_fine_terapia));
            else $this->fileH2[$z]['data_fine_periodo_fatturazione'] = date('dmY', strtotime($data_fine_mese_attuale));

            $this->fileH2[$z]['num_registro'] = $this->fileH1[$z]['anno_rif'] . decifra($_SESSION['cod_asl']) . '0' . date('y') . str_pad($_POST['mese'], 2, '0', STR_PAD_LEFT) . '0001';;      // "203021040001";      // AGGIUNGERE numero registro struttura
            $this->fileH2[$z]['medico_autorizz'] = '                ';
            $this->fileH2[$z]['cod_medico_autorizz'] = '       ';
            $this->fileH2[$z]['istat_primo_ricovero'] = '000000';
            $this->fileH2[$z]['progressivo'] = $this->fileH1[$z]['progressivo'];

        

        $this->fileH2[$z]['posizione_contab'] = ' ';
        $this->fileH2[$z]['err01'] = ' ';
        $this->fileH2[$z]['err02'] = ' ';
        $this->fileH2[$z]['err03'] = ' ';
        $this->fileH2[$z]['err04'] = ' ';
        $this->fileH2[$z]['err05'] = ' ';
        $this->fileH2[$z]['err06'] = ' ';
        $this->fileH2[$z]['err07'] = ' ';
        $this->fileH2[$z]['err08'] = ' ';
        $this->fileH2[$z]['err09'] = ' ';
        $this->fileH2[$z]['err10'] = ' ';

        $this->fileH2[$z]['anno_rif'] = $this->fileH1[$z]['anno_rif'];
        $this->fileH2[$z]['cod_strut_erog'] = decifra($_SESSION['cod_struttura_eroga']);
        $this->fileH2[$z]['identificativo_mensile'] = $this->fileH1[$z]['identificativo_mensile'];

        $this->fileH2[$z]['anno_mese_invio'] = date('Ym');
        $this->fileH2[$z]['asl_addebito'] = 000;
    $do_sql->close();

比以前好,但仍然不能接受

更新 2: 经过一些测试,我发现这个查询COUNT(id) FROM tbl_pazienti_terapie_presenze WHERE MONTH(ingresso_effettuato) = ? AND id_paziente = ? 导致严重减速。我不知道为什么会这样。

解决方案: 谢谢大家的回答。 问题是查询函数 MONTH()。尽管该字段已编入索引,但函数 MONTH() 会跳过索引并因此减慢查询速度。 将其替换为 例如WHERE ingresso_effettuato BETWEEN '2021-12-01 00:00:00', '2021-12-31 23:59:59'WHERE ingresso_effettuato &gt;= '2021-12-01 00:00:00' AND ingresso_effettuato &lt;= '2021-12-31 23:59:59' 问题已解决。

【问题讨论】:

小点 将 IF 语句塞进一行并不能加快速度,只会减慢跟随你的可怜的开发人员的速度,并且必须弄清楚发生了什么 请告诉我们SHOW KEYS FROM tbl_pazienti_terapie_menomazioni的结果。请为您使用的每张桌子。 您尝试过检查哪些部分速度较慢?您尝试过什么来解决问题本身? if (x !== NULL || x != '') 的一般逻辑总是正确的。可能意味着使用&amp;&amp; 如果您遇到任何与数据库相关的问题,请分享更多详细信息,例如慢查询的执行计划 【参考方案1】:

您的代码运行缓慢的原因有很多。

php 中,这通常是由滥用数据库引起的。首先,您需要在表上设置索引,尤其是对于您用于 SELECT 语句的字段。

此外,由于 PHP 必须通过网络连接到数据库,如果您的数据库位于与您的网络服务器不同的服务器上,您的代码可能会更慢。一些托管服务提供商对数据库使用不同的网络。因此,尽可能少地调用数据库至关重要。

在您的脚本中,我看到您使用了 prepare()、execute()、close() 和这 3 次。这意味着您进入数据库并在那里执行一些操作。这也可能导致性能下降。

我做了一个小小的性能测试,表明在循环中使用 preapre 语句和在 ouside 中使用 preapre 语句是有区别的:

<?php
error_reporting(E_ALL);
ini_set('display_errors','On');

$mysqli = new mysqli('db','db','db','db');

$queries = 100;

$start = microtime(true);

$resultId = null;
$resultTest = null;
for($i=0;$i<=100;$i++)
    $id = 1;

    $sql = "SELECT id,test FROM test WHERE id = ?";
    $statement = $mysqli->prepare($sql);
    $statement->bind_param('i',$id);
    $statement->execute();
    $statement->store_result();
    $statement->bind_result($resultId,$resultTest);
    $statement->close();

    $id = 2;
    $sql = "SELECT id,test FROM test WHERE id = ?";
    $statement = $mysqli->prepare($sql);
    $statement->bind_param('i',$id);
    $statement->execute();
    $statement->store_result();
    $statement->bind_result($resultId,$resultTest);
    $statement->close();

    $id = 3;
    $sql = "SELECT id,test FROM test WHERE id = ?";
    $statement = $mysqli->prepare($sql);
    $statement->bind_param('i',$id);
    $statement->execute();
    $statement->store_result();
    $statement->bind_result($resultId,$resultTest);
    $statement->close();



$end = microtime(true);
$diff = $end-$start;
echo "Prepare statements inside loop time: ".$diff."<br/>";


$start = microtime(true);

 $sql = "SELECT id,test FROM test WHERE id = ?";
$statement1 = $mysqli->prepare($sql);
$statement2 = $mysqli->prepare($sql);
$statement3 = $mysqli->prepare($sql);

for($i=0;$i<=100;$i++)
    $id = 1;


    $statement1->bind_param('i',$id);
    $statement1->execute();
    $statement1->store_result();
    $statement1->bind_result($resultId,$resultTest);


    $id = 2;

    $statement2->bind_param('i',$id);
    $statement2->execute();
    $statement2->store_result();
    $statement2->bind_result($resultId,$resultTest);


    $id = 3;

    $statement3->bind_param('i',$id);
    $statement3->execute();
    $statement3->store_result();
    $statement3->bind_result($resultId,$resultTest);



$statement1->close();
$statement2->close();
$statement3->close();

$end = microtime(true);
$diff = $end-$start;
echo "Prepare statements only execute time: ".$diff."<br/>";

结果是

Prepare statements inside loop time: 0.046118021011353
Prepare statements only execute time: 0.020095109939575

所以我建议首先将您的语句移出循环,然后检查您的索引。

这样做。您可以用 Real 值写下您的 SQL 查询之一,并在 PHPMyadmin 中使用 DESCRIBE 执行您的 SELECT 语句

例如

DESCRIBE SELECT COUNT(id) FROM tbl_pazienti_terapie_presenze WHERE MONTH(DATE(ingresso_effettuato)) = 12 AND id_paziente = 1336

在结果中你会看到是否有索引被使用,如果没有,那么你需要为这个字段创建索引。

而且 SQL 查询通常可以与连接组合,因为您在 3 个不同的 SQL 查询中重复使用 ID,这些表必须以某种方式关联,以便它们可以连接到一个语句中。

我不确定这个答案是否能解决您的速度问题,但至少您有一些可以优化的线索。

【讨论】:

您不必这样做,但这是一种更好的方法 @RiggsFolly 但他在循环中有 3 个准备好的语句,并且他在循环中关闭了连接,我很确定他的每次连接都可能出现性能问题 是的,你是对的,但是这个词必须假设如果你不这样做就会有错误并且不会有错误,只是一个缓慢的循环:) 请分享更多细节,以便其他人可以从您的回答中学习。这真的是慢循环的修复吗?还是只是应该作为评论发布的附带评论? 另外,在不知道$this-&gt;centro-&gt;prepare 返回什么的情况下,猜测调用close 将真正关闭整个数据库连接。例如,PHP 自己的 PDO 类没有内置函数称为close

以上是关于非常慢的循环PHP的主要内容,如果未能解决你的问题,请参考以下文章

你将如何优化这个简短但非常慢的 Python 循环?

matlab处理循环特别慢的问题

加快python中的numpy循环?

在这个 PHP foreach 循环中可能遗漏了一些非常简单的东西

求 php 循环执行大量数据 解决办法。

PHP 循环打印数组