Совет по возвращению результатов из большого запроса, возвращающего ошибку. Допустимый размер памяти 734003200 байт исчерпан

Я пытаюсь вернуть результат объединения трех таблиц, чтобы пользователь мог загрузить их в формате CSV, и это вызывает ошибку:

Allowed memory size of 734003200 bytes exhausted

Это выполняется запрос:

SELECT *
FROM `tblProgram`
JOIN `tblPlots` ON `tblPlots`.`programID`=`tblProgram`.`pkProgramID`
JOIN `tblTrees` ON `tblTrees`.`treePlotID`=`tblPlots`.`id`

Строка кода, вызывающая ошибку, такова:

$resultsALL=$this->db->query($fullQry);

Где $fullQry — это запрос, показанный выше. Когда я комментирую эту единственную строку, все работает без ошибок. Так что я уверен, что это не бесконечный цикл, который мне не хватает.

Мне интересно, как мне разбить запрос, чтобы я мог получить результаты без ошибок? Сейчас в таблицах содержится относительно небольшой объем данных, и в конечном итоге они будут еще больше, поэтому я не думаю, что увеличение объема памяти является хорошим вариантом.

Я использую CodeIgniter/php/mysql. Я могу предоставить больше кода, если нужно...

Спасибо за любое направление, которое вы можете посоветовать!

Какую версию CodeIgniter вы используете?

steven7mwesigwa 17.03.2022 17:06

Я не знаю codeigniter, но я предполагаю, что используются буферизованные запросы. Это фиксирует полный набор результатов, используйте небуферизованный, если это возможно, и он будет запускаться только 1 в строке. У вас все равно может не хватить памяти, если вы сохраняете все результаты. Также вам нужен каждый столбец? замена * нужными вам столбцами сократит потребление памяти.

user3783243 17.03.2022 17:12

Я на CodeIgniter 3.1.11

xanabobana 17.03.2022 21:17

Я хотел бы предоставить все поля, если это возможно - это для того, чтобы пользователь мог загрузить доступные данные. На самом деле я пытался просто вытащить несколько полей и все равно получаю ошибку нехватки памяти.

xanabobana 17.03.2022 21:18

@xanabobana CodeIgniter 3 использует $this->db->query("ВАШ ЗАПРОС")->unbuffered_row()

steven7mwesigwa 17.03.2022 21:31

@steven7mwesigwa Я пытался использовать $dbquery->fetch(PDO::FETCH_ASSOC) и unbuffered_row(), но все равно получаю ту же ошибку нехватки памяти :( Должен быть способ сделать это!

xanabobana 18.03.2022 17:27

@xanabobana Хорошо. Спасибо за ответ. Пожалуйста, взгляните на мой второй ответ еще раз. Я исправил проблему «временная таблица не сохраняется».

steven7mwesigwa 18.03.2022 18:47
Стоит ли изучать PHP в 2023-2024 годах?
Стоит ли изучать PHP в 2023-2024 годах?
Привет всем, сегодня я хочу высказать свои соображения по поводу вопроса, который я уже много раз получал в своем сообществе: "Стоит ли изучать PHP в...
Symfony Station Communiqué - 7 июля 2023 г
Symfony Station Communiqué - 7 июля 2023 г
Это коммюнике первоначально появилось на Symfony Station .
Оживление вашего приложения Laravel: Понимание режима обслуживания
Оживление вашего приложения Laravel: Понимание режима обслуживания
Здравствуйте, разработчики! В сегодняшней статье мы рассмотрим важный аспект управления приложениями, который часто упускается из виду в суете...
Установка и настройка Nginx и PHP на Ubuntu-сервере
Установка и настройка Nginx и PHP на Ubuntu-сервере
В этот раз я сделаю руководство по установке и настройке nginx и php на Ubuntu OS.
Коллекции в Laravel более простым способом
Коллекции в Laravel более простым способом
Привет, читатели, сегодня мы узнаем о коллекциях. В Laravel коллекции - это способ манипулировать массивами и играть с массивами данных. Благодаря...
Как установить PHP на Mac
Как установить PHP на Mac
PHP - это популярный язык программирования, который используется для разработки веб-приложений. Если вы используете Mac и хотите разрабатывать...
1
8
77
2
Перейти к ответу Данный вопрос помечен как решенный

Ответы 2

Use getUnbufferedRow() for processing large result sets.

getUnbufferedRow()

This method returns a single result row without prefetching the whole result in memory as row() does. If your query has more than one row, it returns the current row and moves the internal data pointer ahead.

$query = $db->query("YOUR QUERY");

while ($row = $query->getUnbufferedRow()) {
    echo $row->title;
    echo $row->name;
    echo $row->body;
}

For use with MySQLi you may set MySQLi’s result mode to MYSQLI_USE_RESULT for maximum memory savings. Use of this is not generally recommended but it can be beneficial in some circumstances such as writing large queries to csv. If you change the result mode be aware of the tradeoffs associated with it.

$db->resultMode = MYSQLI_USE_RESULT; // for unbuffered results

$query = $db->query("YOUR QUERY");

$file = new \CodeIgniter\Files\File(WRITEPATH.'data.csv');

$csv = $file->openFile('w');

while ($row = $query->getUnbufferedRow('array'))
{
    $csv->fputcsv($row);
}

$db->resultMode = MYSQLI_STORE_RESULT; // return to default mode

Примечание:

When using MYSQLI_USE_RESULT all subsequent calls on the same connection will result in error until all records have been fetched or a freeResult() call has been made. The getNumRows() method will only return the number of rows based on the current position of the data pointer. MyISAM tables will remain locked until all the records have been fetched or a freeResult() call has been made.

You can optionally pass ‘object’ (default) or ‘array’ in order to specify the returned value’s type:

$query->getUnbufferedRow();         // object
$query->getUnbufferedRow('object'); // object
$query->getUnbufferedRow('array');  // associative array

freeResult()

It frees the memory associated with the result and deletes the result resource ID. Normally PHP frees its memory automatically at the end of script execution. However, if you are running a lot of queries in a particular script you might want to free the result after each query result has been generated in order to cut down on memory consumption.

$query = $thisdb->query('SELECT title FROM my_table');

foreach ($query->getResult() as $row) {
    echo $row->title;
}

$query->freeResult(); // The $query result object will no longer be available

$query2 = $db->query('SELECT name FROM some_table');

$row = $query2->getRow();
echo $row->name;
$query2->freeResult(); // The $query2 result object will no longer be available

Благодарю вас! К сожалению, я все еще получал ту же ошибку, когда пробовал это.

xanabobana 17.03.2022 21:19
Ответ принят как подходящий

На основе: MySQL: получить большой выбор по частям

Вы также можете попробовать получить данные порциями, используя предложение LIMIT.

Поскольку вы используете CodeIgniter 3, вот как вы можете это сделать.

Возможно, вам придется передать другой $orderByаргумент № 6 методу getChunk(...), если ваши объединенные таблицы имеют конфликтующие id имена столбцов.

То есть: $this->getChunk(..., ..., ..., 0, 2000, "tblProgram.id");

Решение:

<?php

class Csv_model extends CI_Model
{
    public function __construct()
    {
        parent::__construct();
        $this->load->database();
    }

    public function index()
    {
        $sql = <<< END
SELECT *
FROM `tblProgram`
JOIN `tblPlots` ON `tblPlots`.`programID`=`tblProgram`.`pkProgramID`
JOIN `tblTrees` ON `tblTrees`.`treePlotID`=`tblPlots`.`id`
END;

        $this->getChunk(function (array $chunk) {
            /*
             * Do something with each chunk here;
             * Do something with each chunk here;
             * log_message('error', json_encode($chunk));
             * */
        }, $this->db, $sql);
    }

    /*
     * Processes a raw SQL query result in chunks sending each chunk to the provided callback function.
     * */
    function getChunk(callable $callback, $DBContext, string $rawSQL = "SELECT 1", int $initialRowOffset = 0, int $maxRows = 2000, string $orderBy = "id")
    {
        $DBContext->query('DROP TEMPORARY TABLE IF EXISTS chunkable');
        $DBContext->query("CREATE TEMPORARY TABLE chunkable AS ( $rawSQL ORDER BY `$orderBy` )");

        do {

            $constrainedSQL = sprintf("SELECT * FROM chunkable ORDER BY `$orderBy` LIMIT %d, %d", $initialRowOffset, $maxRows);
            $queryBuilder = $DBContext->query($constrainedSQL);
            $callback($queryBuilder->result_array());
            $initialRowOffset = $initialRowOffset + $maxRows;

        } while ($queryBuilder->num_rows() === $maxRows);
    }

}

Благодарю вас! Я получаю сообщение об ошибке, что таблица chunkable не существует в первом SELECT * FROM chunkable. Почему временная таблица не сохраняется достаточно долго, чтобы выполнить на ней этот выбор?

xanabobana 18.03.2022 17:04

@xanabobana Извините за ошибку! Я не сталкивался с этим до сегодняшнего дня. В любом случае исправление довольно простое. Проверьте мой отредактированный ответ. Я просто изменил ->simple_query(...) на ->query(...) Дайте мне знать, если это сработает для вас. Удачи!

steven7mwesigwa 18.03.2022 18:42

спасибо @steven7mwesigwa. Я получаю сообщение об ошибке «Отказано в доступе для пользователя 'vmc_writer'@'%' к базе данных 'VMC_nefin'» теперь при попытке создать таблицу. Я попытался предоставить привилегии в phpmyadmin, используя «GRANT ALL ON . TO 'vmc_writer'@'%'; GRANT SELECT, INSERT ON . TO 'vmc_writer'@'%';" но я получаю сообщение «Отказано в доступе для пользователя 'vmc_admin'@'%' (используя пароль: ДА)». Означает ли это, что мне нужно запрашивать у администратора БД другие привилегии?

xanabobana 18.03.2022 21:41

@xanabobana Да. Конкретно GRANT CREATE TEMPORARY TABLES ON *.* TO 'vmc_writer'@'%';

steven7mwesigwa 19.03.2022 03:54

@xanabobana Эта команда должна быть запущена учетной записью пользователя MySQL/MariaDB, которая имеет возможность предоставлять привилегии.

steven7mwesigwa 19.03.2022 03:57

Другие вопросы по теме