Yii2: batch
15 февраля 2014
При работе с большим количеством данных важно не использовать слишком много памяти. Сегодня Yii2 обзавёлся решением. Работает за раз не со всеми данными, а частями:
use yii\db\Query; $query = (new Query) ->from('tbl_user') ->orderBy('id'); foreach ($query->each() as $user) { // $user — одна строка из tbl_user } foreach ($query->batch(10) as $users) { // $users — массив из 10 строк }
То же работает с Active Record:
foreach (Customer::find()->batch() as $customers) { // $customers — массив из 10 или менее объектов Customer }
Комментарии RSS по email OK
В последнем примере полагаю должно быть ->batch(10) ?
Да, это мегатема вообще.
Кстати, не стоит ли такие штуки анонсировать в (прикрепленной?..) ветке англофорума? Ну, я понимаю что пока бета и всё такое, но все нововведения, в основном, втихую проходят.
Те, кто на v1 до сих пор сидит - они же за гитхабом не следят особо: ну коммит и коммит, релиза нет же, значит сидим дальше ровно. А так народ будет видеть, что движуха крутейшая происходит, и всерьез задумаются об апгрейде до v2.
ну версию 2 боясно использовать на продакшене, а так слежу - и вот проект джаст фо фан планирую на новой версии делать
Эм, а может кто объяснить как это работает? Я что-то профита не понимаю...
serg, объяснить как работает или в чем профит?
Как работает, смотри в последние коммиты на гитхаб.
Профит - а вы SELECT * на табличку больше гигабайта когда делали в последний раз? А именно это и произойдет при работе с ->all()
Как это решалось раньше: new Paginator -> new DataProvider и оп, вроде бы по памяти мы не течемм, но MySql начинает нам говорить - что мы делаем на каждую "страницу" отдельный запрос, который мускуль честно выполняет. Как это сделано сейчас: MySql выполняет запрос SELECT * но в PDO возвращает указатель на результат, и когда мы просим следующий ->batch() то PDO просто просит у MySql данные со следующей позиции указателя.
Возможно немного сумбурно, но вроде так.
Sam, следует ли нам ждать Batch Save?
Алексей, batch insert/update уже есть. Над
save
думали, но пока ничего нормально не придумали.А почему было решено сделать именно отдельный метод, вместо параметра в each()?
Ведь можно было сделать, чтобы each(10) возвращал Iterable объект, который скрывал бы в себе эту логику(отбирал бы по 10 записей и потом по одной бы их возвращал).
В таком случае переход от полной и batch выборкой делался бы добавлением всего одного числа и не было бы необходимости в еще одном внутреннем цикле.
А возможность выбирать данные порциями нужна для какого то другого функционала фреймворка? Если да, то какого?
А то не вижу особого профита от этой фичи по сравнению с :
Имхо оберточка над $query->createCommand()->query() больше бы не помешала для Query объекта.
Полезно, спасибо
maleks, так это вроде на уровне Query объекта и DataReader и сделано.
Для insert нашел batchInsert, а для update есть команда для пакетного обновления?
Андрей, для обновления нет потому как сильно специфично для разного рода баз. Например, для MySQL выглядит это так:
Здравствуйте, при попытки выполнить foreach (Item::find()->each() as $item) {} на табличке с 200 тысячами записей вываливается ошибка о нехватке памяти. Сам цикл ни разу не выполняется. PDO работает, памяти 1гб. Что я могу делать не так?
Если разница только в PDO, то скорее всего вы делаете всё так и в Yii где-то недоработка.
Вобщем, не работает магия batch() для больших резалт сетов. Судя по реализации yii\db\BatchQueryResult - и не должна была.
Итак, есть таблица users размером 77 МиБ (240000 записей) и следующий код:
Окружение: MySQL 5.5.44, PHP 5.5.9, memory_limit=128M, Yii 2.0.6
Результаты запуска:
Объяснение простое - buffered results. По умолчанию результаты всех запросов буфферизуются. Это значит, что результат всей выборки сначала целиком передаётся клиенту (в память PHP процесса), а уже потом производится работа с ним.
Результат запроса хранится вунутри mysql модуля в собственной структуре данных. Разница в том, что в mysqlnd эта структура контролируется менеджером памяти PHP, и поэтому её размер учитывается в memory_get_[peak_]usage() и влияет на достижение memory_limit. Из-за этого #3 валится при любых настройках $batch_size (до цикла foreach выполнение даже не доходит).
Экономия памяти начинает работать только при установке настройки:
Настройка отключает буфферизацию результатов. Это означает, что актуальные данные будут отправляться mysql сервером только по мере продвижения курсора по result set'у. Поштучно. В этом случае и libmysql, и mysqlnd будут показывать одинаковые результаты по памяти при одинаковых размерах $batch_size. При этом "утечки" памяти при использовании libmysql происходить не будут.
С другой стороны, это налагает дополнительную нагрузку на mysql сервер и не позволяет совершать другие SQL запросы через данное соединение, не закрыв предварительно наш небуфферизованный результат. Последнее как раз сильно ограничивает меня при переносе одной легаси системы на yii. Старый код работает с буфферизованными результатами и MySQL сервер настраивался на эту модель использования.
В чём же тогда экономия памяти, предоставляемая yii\db\Query::batch()? По сути, "экономия" происходит только при использовании драйвера libmysql и буфферизованных запросов. Когда только часть результата копируется из result set'а, хранящегося внутри драйвера, в PHP массив.
Экономия в кавычках, потому что нет никакой экономии. Здесь присутствует утечка памяти и, описанная мной выше, бомба замедленного действия.
Как верно заметил maleks, фича не особенно полезна. Более того, она сбивает с толку, потому что её описание из The Definitive Guide to Yii 2.0 намекает на автоматическое использование LIMIT/OFFSET окна размером $batch_size. По крайней мере, люди понимают эту фичу именно так, о чем свидетельствуют нагугленные мной треды на форумах и SO.
Саня, весь смысл в том, что читается одновременно
$batch_size
элементов. Поставьте в сотню и попробуйте считать 10000 строк. По памяти пройдёт.Поясните подробнее, конкретно откуда читается одновременно
$batch_size
элементов? Из PDOStatement (если снять всю шелуху)?В процессе изучения компонентов yii\db я столкнулся с проблемой, описанной на форуме yiiframework.ru. Готового решения не нашел, поэтому пришлось проводить исследование самостоятельно, результаты которого и оформил комментарием к этому блог посту.
Значение "любой" в приведённой ранее табличке означает действительно любое значение. Хоть 100, хоть 1.
Попробуйте повторить тест самостоятельно.
Я не стал бы так подробно распинаться, не считая это упущением фреймворка и не разобравшись с вопросом до конца. Свою изначальную проблему я уже решил другими средствами, однако, надеюсь, что Вы, как коммитер в yii framework, сможете разобраться и исправить сложившуюся ситуацию вокруг batch()/each().
В моем случае, спасла последняя версия PDO и PHP, как и написал Александр в том топике. Суть как раз в том что pdo возвращает ссылку на данные, а уже yii выдергивает нужные порции.
Если Вы явным образом не прописывали
PDO::MYSQL_ATTR_USE_BUFFERED_QUERY => false
, то Ваш код обязательно сломается при переходе с libmysqlclient на mysqlnd. Это лишь вопрос времени. Потом будете сидеть и удивляться.Прям канонический пример проблемы "почему локально всё работает, а на сервере - нет?" :-D
Саня, сталкивался с такой же проблемой. Решил пока дополнительным циклом для обхода моделей.
сегодня столкнулся с проблемой нехватки памяти при
->batch()
Гляжу посту уже 5 лет, как-то решена эта проблема?
Вроде бы помогло
Yii::$app->db->pdo->setAttribute(\PDO::MYSQL_ATTR_USE_BUFFERED_QUERY, false);
потом все упало в другом месте:
yii2 Cannot execute queries while other unbuffered queries are active.
Собственно, это одна из особенностей использования
MYSQL_ATTR_USE_BUFFERED_QUERY = false
, о которой я писал 4 года назад:Перед тем как выполнять какие-либо запросы в другом месте, необходимо сначала прочитать до конца результат, возвращаемый методом
->batch()
, либо выполнять запросы через другой коннект, например(new Query())->select('*')->from('table')->createCommand(Yii::$app->db2);
Всем привет. У меня точно такая же проблема, методы
->each и ->batch
работают не так как ожидается и падают по памяти. При ходится делать через дополнительные циклы лимиты и офсеты. Когда это будет исправлено???
Код исправлять уже не будут. Решили ограничиться упоминанием в доке. Причём только в английской версии. Возможно исправят в yii3, но я бы не надеялся.
"решается" только лимитами, т.к. это проблема не framework, а PDO.
NemoCap, тем не менее, фреймворк зачем-то попытался "решить" эту проблему и получилось узкоспециализированное решение с большим количеством подводных камней.