Я пытаюсь вернуть результат объединения трех таблиц, чтобы пользователь мог загрузить их в формате 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, но я предполагаю, что используются буферизованные запросы. Это фиксирует полный набор результатов, используйте небуферизованный, если это возможно, и он будет запускаться только 1 в строке. У вас все равно может не хватить памяти, если вы сохраняете все результаты. Также вам нужен каждый столбец? замена *
нужными вам столбцами сократит потребление памяти.
Я на CodeIgniter 3.1.11
Я хотел бы предоставить все поля, если это возможно - это для того, чтобы пользователь мог загрузить доступные данные. На самом деле я пытался просто вытащить несколько полей и все равно получаю ошибку нехватки памяти.
@xanabobana CodeIgniter 3 использует $this->db->query("ВАШ ЗАПРОС")->unbuffered_row()
@steven7mwesigwa Я пытался использовать $dbquery->fetch(PDO::FETCH_ASSOC) и unbuffered_row(), но все равно получаю ту же ошибку нехватки памяти :( Должен быть способ сделать это!
@xanabobana Хорошо. Спасибо за ответ. Пожалуйста, взгляните на мой второй ответ еще раз. Я исправил проблему «временная таблица не сохраняется».
Use
getUnbufferedRow()
for processing large result sets.
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 afreeResult()
call has been made. ThegetNumRows()
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 afreeResult()
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
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
Благодарю вас! К сожалению, я все еще получал ту же ошибку, когда пробовал это.
На основе: 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 Извините за ошибку! Я не сталкивался с этим до сегодняшнего дня. В любом случае исправление довольно простое. Проверьте мой отредактированный ответ. Я просто изменил ->simple_query(...)
на ->query(...)
Дайте мне знать, если это сработает для вас. Удачи!
спасибо @steven7mwesigwa. Я получаю сообщение об ошибке «Отказано в доступе для пользователя 'vmc_writer'@'%' к базе данных 'VMC_nefin'» теперь при попытке создать таблицу. Я попытался предоставить привилегии в phpmyadmin, используя «GRANT ALL ON . TO 'vmc_writer'@'%'; GRANT SELECT, INSERT ON . TO 'vmc_writer'@'%';" но я получаю сообщение «Отказано в доступе для пользователя 'vmc_admin'@'%' (используя пароль: ДА)». Означает ли это, что мне нужно запрашивать у администратора БД другие привилегии?
@xanabobana Да. Конкретно GRANT CREATE TEMPORARY TABLES ON *.* TO 'vmc_writer'@'%';
@xanabobana Эта команда должна быть запущена учетной записью пользователя MySQL/MariaDB, которая имеет возможность предоставлять привилегии.
Какую версию CodeIgniter вы используете?