【问题标题】:Memory Allocation, Bytes Exhausted PHP/LARAVEL内存分配,字节耗尽 PHP/LARAVEL
【发布时间】:2020-02-05 22:03:45
【问题描述】:

大家好,

我正在使用Laravel Excel/Maatwebsite 开发一个系统。我想要实现的是当用户将一个excel文件插入系统时,系统会检查一些东西,然后将数据插入到数据库中。

这是数据库模式的一个实例:

Hadees:
h_id | h_english | r_id | h_last_raavi_id | b_id | s_id | k_id | h_status

Raavi:
r_id | r_english | r_status

Baab:
b_id | b_english | s_id | b_status

Section:
s_id | s_english | k_id | s_status

Kitaab:
k_id | k_english | k_status

我的控制器:

use Illuminate\Http\Request;
use App\Http\Controllers\Controller;
use App\Models\Section;
use App\Models\Raavi;
use App\Imports\HadeesImport;
use Excel;

class ImportHadeesController extends Controller{
    /**
     * Show the application import-hadees page.
     *
     * @return \Illuminate\Http\Response
     */
    public function index(){
        $section = Section::where(['s_status' => 1])->get();

        return view('admin/import-hadees', compact('section'));
    }

    /**
     * This method uses the Excel facade to prep the excel file 
     * and extract data from it and uses App\Imports\HadeesImport 
     * class to insert each row in the database schema accordingly.
     * 
     * @param Illuminate\Http\Request $request
     * @return \Illuminate\Http\Response
     */
    public function importHadees(Request $request){
        $raavi = Raavi::where(['r_status' => 1])->get();

        if($request->file('hadees_sheet')) {
        } else {
           return response()->json(['status'=>'error', 'msg'=> 'No file present!']);
        }
    
        $temp = $request->file('hadees_sheet')->store('temp'); 
        $path=storage_path('app').'/'.$temp;

        $hadees = Excel::import(new HadeesImport($request->s_id, compact('raavi')), $path);

        if($hadees){
            return response()->json(['status'=>'success', 'msg'=> 'Successfully imported all the data from the file to the database!']);
        } else{
            return response()->json(['status'=>'error', 'msg'=> 'Unable to import data from the file to the database!']);
        }
    }
}

HadeesImport 类:

use Illuminate\Support\Collection;
use Maatwebsite\Excel\Concerns\ToCollection;
use Maatwebsite\Excel\Concerns\WithHeadingRow;
use App\Models\Section;
use App\Models\Baab;
use App\Models\Hadees;
use App\Models\Raavi;

class HadeesImport implements ToCollection, WithHeadingRow{
    /**
     * Global variable for section_id.
     */
    public $s_id;

    /**
     * Global variable for raavi's data.
     */
    public $raavi;

    /**
     * Construction function.
     * 
     * @param int $id
     */
    function __construct($id, $arr) {
        $this->s_id = $id;
        $this->raavi = $arr;
    }

    /**
    * This method is responsible for inserting the excel
    * sheet's rows data to the database schema.
    * 
    * @param Collection $row
    */
    public function collection(Collection $rows){
        $baab = Baab::where(['s_id' => $this->s_id])->get();
        $hissa = Section::where(['s_id' => $this->s_id])->first();
        $kitaab = $hissa->k_id;
        $first_raavi = 0;
        $last_raavi = 0;
        $baab_id = 0;
        $data = array();

        foreach ($rows as $row){
            if($row['hadees_arabic'] != "" && $row['hadees_urdu'] != ""){
                $baab_id = $this->baabCheck($baab, $row);
            
                foreach($this->raavi['raavi'] as $rav){
                    if(trim($rav->r_english) == trim($row['first_raavi_english'])){ 
                        $first_raavi = $rav->r_id; 
                    } else{
                        $first_raavi = 0;
                    }
    
                    $last_raavi = (trim($rav->r_english) == trim($row['last_raavi_english']))? $rav->r_id : 0;
                }

                if($first_raavi == 0){
                    $raavi = Raavi::create([
                        'r_arabic' => trim($row['first_raavi_urdu']),
                        'r_urdu' => trim($row['first_raavi_urdu']),
                        'r_english' => trim($row['first_raavi_english']),
                        'r_nickname' => trim($row['raavi_other_names']),
                        'r_status' => 1,
                    ]);

                    $first_raavi = $raavi->r_id;
                }

                if($last_raavi == 0){
                    $raavi = Raavi::create([
                        'r_arabic' => trim($row['last_raavi_urdu']),
                        'r_urdu' => trim($row['last_raavi_urdu']),
                        'r_english' => trim($row['last_raavi_english']),
                        'r_nickname' => 'n/a',
                        'r_status' => 1,
                    ]);

                    $last_raavi = $raavi->r_id;
                }

                $data = array([
                    'h_arabic' => trim($row['hadees_arabic']),
                    'h_urdu' => trim($row['hadees_urdu']),
                    'h_english' => trim($row['hadees_english']),
                    'h_roman_keywords' => trim($row['roman_keywords']),
                    'h_number' => trim($row['hadees_number']),
                    'r_id' => $first_raavi,
                    'h_last_raavi_id' => $last_raavi,
                    'b_id' => $baab_id,
                    's_id' => $this->s_id,
                    'k_id' => $kitaab,
                    'h_status' => 1
                ]);
            }
        }

        $hadees = Hadees::create($data);
    }

    /**
    * Checks if the baab exists in the database or not.
    * 
    * @param Collection $baab
    * @param Object $row
    * @return int - baad_id or 0
    */
    public function baabCheck($baab, $row){
        foreach($baab as $b){
            if(trim($b->b_arabic) == trim($row['baab_arabic']) || trim($b->b_urdu) == trim($row['baab_urdu']) || trim($b->b_english) == trim($row['baab_english'])){
                return $b->b_id;
            } else{
                return 0;
            }
        }
    }
}

当数据较少时,一切正常。现在我在 Raavi 表中有 1400+ 行,在 Baab 表中有 10,000+ 行。现在,每当我尝试将工作表导入系统时,它都会给我这个错误:

允许的内存大小为 268435456 字节已用尽(尝试分配 37748736 字节)。

我认为这是因为 foreach() 循环太长了。任何形式的帮助将不胜感激。如果你们对糟糕的编码或糟糕的逻辑构建有任何建议,请告诉我。我已经在这个问题上停留了将近两天。提前致谢。

P.s:本地主机和主机上的错误相同,只是字节数不同。这是由于不同的 memory_limit 设置,我相信。

【问题讨论】:

    标签: php laravel localhost hosting maatwebsite-excel


    【解决方案1】:

    所有发布的解决方案都提到提高 PHP 的内存限制。

    它不是那样工作的。你不能只是在一个问题上投入更多的内存。如果您的服务器有 2GB 的 RAM,并且您上传了一个文件,其中创建了所有阵列,可以使用超过 2GB 的 RAM,该怎么办?下一步是什么?更不用说服务器耗尽内存并杀死其他进程的风险。例如,如果服务器共享运行的 PHP 和 MySQL,并且 PHP 导致服务器内存不足,OOM Killer 将启动并可能杀死 MySQL 进程。

    解决您的问题的方法是分块处理该文件。例如,Laravel 6 有一个新的集合类型,Lazy Collections。他们可以帮助您加快速度。您的代码可能必须更改才能使用块处理,但恕我直言,这是解决此问题的唯一方法。

    我也会在队列中运行它,而不是根据用户请求。

    我也知道你使用的包支持chunking读取和batching插入。

    【讨论】:

    • 我也是这么想的。因为为脚本允许更多内存不是一个好的解决方案。问题是我不知道如何以块的形式分发文件,然后继续将块上传到数据库中。
    • 那么这就是您需要开始阅读的内容。一种方法是将导入仅存储在“临时”表中,例如imports_data。然后,有一个从chunks 中读取的队列作业,或者,就像我说的,惰性集合。然后,通过读取少量行 (100),您应该能够保持较低的内存使用量。
    • 不要害怕提交有关新问题的新问题。你有一个问题,如果你想以正确的方式解决它,你必须挖掘正确的答案。大多数时候,学习新事物来解决您的问题。
    • @Saud,也可以查看package documentation关于分块的信息,也许对你也有帮助
    • 好的,谢谢您的回复。我将开始阅读和搜索有关阅读和写作的块。我已经阅读了关于 lazy collections 的信息,看起来它们可能是一个有用的功能。要使用它们,我必须将我的项目从 5.8 升级到 6.x。如果发生任何事情,我会立即开始并发布。干杯!
    【解决方案2】:

    扩展您的memory_limit。对于'localhost',在php.ini-

    memory_limit=2048M
    

    然后重启你的服务器。

    【讨论】:

    • 数据未插入数据库是另一个问题。让我检查你的代码。
    • 我已经这样做了。有时,只要我提高 php.ini 文件中的限制,它就会停止显示错误,但系统不会在数据库中插入行并显示成功消息。有时它只是说 http server error:500
    • 我绝对不会这样做。有风险的,危险的,1个请求就可以让服务器内存不足。糟糕的解决方案。更好的方法是找到一种方法来在队列中分块处理该文档。当我说块时,我的意思是要读取并插入到所有表中的块
    【解决方案3】:

    您可以将某些文件/页面/脚本的 memory_limit 设置为大于 php.ini 设置的值。添加__construct() 方法并提高内存限制:

    public function __construct() {
    
         ini_set('memory_limit', '1G'); // change as needed, as long as your system can support it
    
         parent::__construct(); // If added in your controller. Probably not needed if you use it in your import class
    
    }
    

    【讨论】:

    • 这样做可以吗,或者安全吗?请指教。
    • 我在一些脚本中一直这样做,因为它们处理大量数据。您只需要确保系统上有足够的可用内存,并且您不会将内存增加到超过将要访问的内存。如果您要在文件上获得很多点击量,那么只需根据需要提高它。这比提高整个系统的内存限制要好,因为它只会在您需要时将限制提高到您需要的范围内。
    • 好的,感谢您的回复。我试过你的回答,现在是:localhost is currently unable to handle this request. HTTP ERROR 500
    • 您需要检查服务器错误日志以获取更多信息,因为 500 是一个非常通用的错误消息。
    • 我检查了它说同样错误的日志:PHP Fatal error: Allowed memory size of 1073741824 bytes exhausted (tried to allocate 32768 bytes)
    【解决方案4】:

    将特定文件的 memory_limit 设置为无限制以覆盖 php.ini memory_limit

    // put it in your construct 
    
    ini_set('memory_limit', -1);
    

    【讨论】:

    • 这样做可以吗?我认为将 memory_limit 设置为无限制不是一个好主意。因为它确保写得不好的脚本不会占用所有内存。
    • 我绝对不会这样做。有风险的,危险的,1个请求就可以让服务器内存不足。糟糕的解决方案。更好的方法是找到一种方法来在队列中分块处理该文档。当我说块时,我的意思是要读取并插入到所有表中的块
    【解决方案5】:

    我最近在运行时遇到了同样的问题

    Model::insert();
    

    我已通过禁用查询日志并取消查询的所有事件来解决此问题。

    DB::connection('your-connection')->disableQueryLog();
    DB::connection('your-connection')->unsetEventDispatcher();
    

    但请注意,它只能在导入数据时使用。 否则,您的其他包的事件和日志记录系统将无法在这两行之后运行您的查询。

    【讨论】:

    • 如果你在使用Model::insert();时遇到同样的错误(内存不足),你可以试试array_chunk()的方法。但是您必须将块大小限制为 MySQL 的 max_allowed_packet
    猜你喜欢
    • 1970-01-01
    • 2010-09-29
    • 2020-06-02
    • 2015-10-01
    • 1970-01-01
    • 2011-01-28
    相关资源
    最近更新 更多