Не работает each

Общие вопросы по использованию второй версии фреймворка. Если не знаете как что-то сделать и это про Yii 2, вам сюда.
Brainfuck
Сообщения: 313
Зарегистрирован: 2018.02.19, 14:20

Не работает each

Сообщение Brainfuck »

Подскажите почему у меня перестал нормально работать each (метод у AcitveQuery выбирающий данные из базы блоками)? Вот пример:

Код: Выделить всё

Yii::beginProfile('test');

$article = Article::findOne(1);

/** @var ArticleComment $comment */
foreach ($article->getComments()->each(2) as $comment) {
	echo "$comment->id ";
}

Yii::endProfile('test');

echo "\n" . implode("\n", ArrayHelper::getColumn(Yii::getLogger()->getProfiling(['yii\db\Command::query']), 'info'));
Выводит

Код: Выделить всё

1 2 3 4 5
SELECT * FROM `articles` WHERE `id`=1
SELECT * FROM `article_comments` WHERE (`article_id`=1)
Т.е. нет разбиения на блоки! Each работает как all!

P.S. Пример на небольшом количестве данных и блоки по 2 элемента, но я заметил эту проблему именно на больших блоках по 100 элементов и мне очень критично чтобы выборка производилась блоками, а не все сразу, т.к. данных много.
skynin
Сообщения: 400
Зарегистрирован: 2017.12.12, 10:09

Re: Не работает each

Сообщение skynin »

Brainfuck писал(а): 2018.07.27, 10:00 Подскажите почему у меня перестал нормально работать each (метод у AcitveQuery выбирающий данные из базы блоками)?

Код: Выделить всё

1 2 3 4 5
SELECT * FROM `articles` WHERE `id`=1
SELECT * FROM `article_comments` WHERE (`article_id`=1)
Т.е. нет разбиения на блоки! Each работает как all!
вообще-то each и сделан для того, чтобы вести себя как all.
во-вторых итератор у BatchQueryResult использует PDO fetch, а не как (телепатирую) вы ожидали limit и offset

отройте код метода each и убедитесь сами что он не шлет новые запросы.
Не желайте странного, и не будет у вас головной боли чтобы достичь этого странного.
Тем более что окажется что оно вам и не нужно было, странное это.
Brainfuck
Сообщения: 313
Зарегистрирован: 2018.02.19, 14:20

Re: Не работает each

Сообщение Brainfuck »

skynin писал(а): 2018.07.27, 10:29 вообще-то each и сделан для того, чтобы вести себя как all.
во-вторых итератор у BatchQueryResult использует PDO fetch, а не как (телепатирую) вы ожидали limit и offset

отройте код метода each и убедитесь сами что он не шлет новые запросы.
Эмм что? Я почти уверен что раньше он таки слал limit и offset... В таком случае в чем его профит перед all?
skynin
Сообщения: 400
Зарегистрирован: 2017.12.12, 10:09

Re: Не работает each

Сообщение skynin »

Brainfuck писал(а): 2018.07.27, 10:33 Эмм что? Я почти уверен что раньше он таки слал limit и offset...
не знаю про раньше. смотрите
yii\db\BatchQueryResult

а также http://php.net/manual/en/pdostatement.fetch.php
Brainfuck писал(а): 2018.07.27, 10:33 В таком случае в чем его профит перед all?
all получает все данные, и для каждой строки создает ActiveRecord или строку (если указать asArray)

batch() и each() запрашиват результаты порционно.
при этом разница - each() построчно.

профит

если у вас цикл скажем на 1000 записей, и в нем есть выход раньше обхода всех, то
each() запросит и создаст ActiveRecord только для тех что вы обрабатывали, а не для всех

если же вы всегда в цикле обрабатываете 1000, то нет смысла all заменять на each.

хотя по расходу памяти тоже есть, потому что одновременно в памяти будет только одна ActiveRecord , а не 1000. теоретически, если вы не сохраняете ссылки на ActiveRecord и если сборщик мусора срабатывает сразу же (что вряд ли)

по другому - each это ленивая загрузка. all - жадная
Не желайте странного, и не будет у вас головной боли чтобы достичь этого странного.
Тем более что окажется что оно вам и не нужно было, странное это.
Brainfuck
Сообщения: 313
Зарегистрирован: 2018.02.19, 14:20

Re: Не работает each

Сообщение Brainfuck »

skynin писал(а): 2018.07.27, 10:51 all получает все данные, и для каждой строки создает ActiveRecord или строку (если указать asArray)

batch() и each() запрашиват результаты порционно.
при этом разница - each() построчно.

профит

если у вас цикл скажем на 1000 записей, и в нем есть выход раньше обхода всех, то
each() запросит и создаст ActiveRecord только для тех что вы обрабатывали, а не для всех

если же вы всегда в цикле обрабатываете 1000, то нет смысла all заменять на each.

хотя по расходу памяти тоже есть, потому что одновременно в памяти будет только одна ActiveRecord , а не 1000. теоретически, если вы не сохраняете ссылки на ActiveRecord и если сборщик мусора срабатывает сразу же (что вряд ли)

по другому - each это ленивая загрузка. all - жадная
Я правда плохо понимаю как работает PDF::fetch. Как черт возьми можно получать данные порционно, не используя при этом limit/offset? Для меня это даже звучит абсурдно...

Так что выгоднее (по скорости и памяти) использовать each или batch? Или примерно одинаково?
skynin
Сообщения: 400
Зарегистрирован: 2017.12.12, 10:09

Re: Не работает each

Сообщение skynin »

Brainfuck писал(а): 2018.07.27, 11:00 Как черт возьми можно получать данные порционно, не используя при этом limit/offset?
объясняю на пальцах:

клиент шлет на сервер БД текст запроса.
при этом клиент говорит - ты мне сразу все не отдавай, может мне все и не понадобится.
сервер БД его транслирует и начинает выполнение, выгребая из файлов БД данные
но помня просьбу, выгребает не все, а останавливает выгребание, и отадет порцию
клиент получив порцию что-то с ней делает. и если мало, шлет серверу
дай еще!
сервер БД с места на котором остановился продолжает выгребать из файлов данные.
и опять шлет порцию.

это - в теории.
в конкретике нюансы зависят от реализации драйвера к БД и реализации сервера БД.
сервер БД может не понимать такой опции: "ты мне сразу все не отдавай, может мне все и не понадобится"
драйвер БД может запрашивать все, но когда получил часть - уже отдавать их потребителю, при этом в фоне получая остальные данные.
и так и дальше, драйвер БД может отдавать порционно, хотя он получил уже весь ответ.
Brainfuck писал(а): 2018.07.27, 11:00 Для меня это даже звучит абсурдно...
для меня нет, потому что понимаю как работают сервера и драйвера в данном случае :)
Brainfuck писал(а): 2018.07.27, 11:00 Так что выгоднее (по скорости и памяти) использовать each или batch? Или примерно одинаково?
зависит от того как вы работаете с данными. плюс еще и от того что написал выше.

например для гридов в админке - мы точно знаем что
нам нужна только порция данных
и такого-то размера
и для таког-то номера страницы.

значит limit offset и all() - самый правильный выбор
"фокусы" с порциями между драйвером и сервером БД нам ничего не дадут, и никак не помогут.
Последний раз редактировалось skynin 2018.07.27, 11:19, всего редактировалось 1 раз.
Не желайте странного, и не будет у вас головной боли чтобы достичь этого странного.
Тем более что окажется что оно вам и не нужно было, странное это.
Brainfuck
Сообщения: 313
Зарегистрирован: 2018.02.19, 14:20

Re: Не работает each

Сообщение Brainfuck »

skynin писал(а): 2018.07.27, 11:11 объясняю на пальцах:

клиент шлет на сервер БД текст запроса.
при этом клиент говорит - ты мне сразу все не отдавай, может мне все и не понадобится.
сервер БД его транслирует и начинает выполнение, выгребая из файлов БД данные
но помня просьбу, выгребает не все, а останавливает выгребание, и отадет порцию
клиент получив порцию что-то с ней делает. и если мало, шлет серверу
дай еще!
сервер БД с места на котором остановился продолжает выгребать из файлов данные.
и опять шлет.

это - в теории.
в конкретике нюансы зависят от реализации драйвера к БД и реализации сервера БД.
сервер БД может не понимать такой опции: "ты мне сразу все не отдавай, может мне все и не понадобится"
драйвер БД может запрашивать все, но когда получил часть - уже отдавать их потребителю, при этом в фоне получая остальные данные.
и так и дальше, драйвер БД может отдавать порционно, хотя он получил уже весь ответ.
Хмм, т.е. по сути это те же самые limit/offset, но без sql-запросов? А зачем вообще усложнять? Почему не сделали на limit/offset? Или так быстрее выходит?
Brainfuck
Сообщения: 313
Зарегистрирован: 2018.02.19, 14:20

Re: Не работает each

Сообщение Brainfuck »

skynin писал(а): 2018.07.27, 11:11 зависит от того как вы работаете с данными. плюс еще и от того что написал выше.

например для гридов в админке - мы точно знаем что
нам нужна только порция данных
и такого-то размера
и для таког-то номера страницы.

значит limit offset и all() - самый правильный выбор
"фокусы" с порциями между драйвером и сервером БД нам ничего не дадут, и никак не помогут.
Я перегоняю данные из одной таблицы в другую. Сейчас кстати открыл для себя batch insert :D
skynin
Сообщения: 400
Зарегистрирован: 2017.12.12, 10:09

Re: Не работает each

Сообщение skynin »

Brainfuck писал(а): 2018.07.27, 11:19 Хмм, т.е. по сути это те же самые limit/offset, но без sql-запросов? А зачем вообще усложнять?
по сути да.

а усложнять - потому что происходит большая экономия на ресурсах процессора и памяти в определенных сценариях.
Brainfuck писал(а): 2018.07.27, 11:19 Почему не сделали на limit/offset? Или так быстрее выходит?
потому что получается большая экономия на ресурсах процессора и памяти в определенных сценариях.

а вопрос
Почему не сделали на limit/offset?
звучит примерно как
почему системы впрыска топлива в современных автомобилях такие сложные?
почему механического карбюратора недостаточно?
Не желайте странного, и не будет у вас головной боли чтобы достичь этого странного.
Тем более что окажется что оно вам и не нужно было, странное это.
Brainfuck
Сообщения: 313
Зарегистрирован: 2018.02.19, 14:20

Re: Не работает each

Сообщение Brainfuck »

Ладно. Кажется все стало ясно. Спасибо что объяснил.
andku83
Сообщения: 988
Зарегистрирован: 2016.07.01, 10:24
Откуда: Харьков

Re: Не работает each

Сообщение andku83 »

skynin писал(а): 2018.07.27, 10:51 если же вы всегда в цикле обрабатываете 1000, то нет смысла all заменять на each.
Противоречивость в одном сообщении: смысл есть и он описан в следующей строке.
skynin писал(а): 2018.07.27, 10:51 хотя по расходу памяти тоже есть, ...
------------------------------------
skynin писал(а): 2018.07.27, 10:51 ... потому что одновременно в памяти будет только одна ActiveRecord , а не 1000.
То количество которое сказано в each(n)
------------------------------------
skynin писал(а): 2018.07.27, 10:51 по другому - each это ленивая загрузка. all - жадная
Не нужно смешивать теплое с мягким, внутри each() может отсутствовать жадная загрузка, а может и присутствовать:
если в запросе передаваемом в each() что-то запрашивать жадно, то оно будет получаться жадно, но отдельно для каждой и порций.
skynin
Сообщения: 400
Зарегистрирован: 2017.12.12, 10:09

Re: Не работает each

Сообщение skynin »

andku83 писал(а): 2018.07.27, 12:20 То количество которое сказано в each(n)
раз исходники вам недоступны
то читаем доку
public yii\db\BatchQueryResult each ( $batchSize = 100, $db = null )
$batchSize int The number of records to be fetched in each batch.

fetched
andku83 писал(а): 2018.07.27, 12:20 Противоречивость в одном сообщении: смысл есть и он описан в следующей строке.
skynin писал(а):
хотя по расходу памяти тоже есть, ...
перечитываем мой пост о том при соблюдении каких условий оно будет
andku83 писал(а): 2018.07.27, 12:20 Не нужно смешивать теплое с мягким, внутри each() может отсутствовать жадная загрузка, а может и присутствовать:
перечитываем мой пост о тонкостях реализации драйвера БД.
там же и про "если в запросе передаваемом в each() что-то запрашивать жадно, то оно будет получаться жадно"
что мы запрашиваем - не влияет на то - как сервер БД и драйвер БД обмениваются по протоколу TCP/IP
Не желайте странного, и не будет у вас головной боли чтобы достичь этого странного.
Тем более что окажется что оно вам и не нужно было, странное это.
andku83
Сообщения: 988
Зарегистрирован: 2016.07.01, 10:24
Откуда: Харьков

Re: Не работает each

Сообщение andku83 »

skynin писал(а): 2018.07.27, 12:24 раз исходники вам недоступны
то читаем доку
public yii\db\BatchQueryResult each ( $batchSize = 100, $db = null )
$batchSize int The number of records to be fetched in each batch.
про то что там по умолчанию используется 100 забыл дописать (хотя собирался). И не пойму чем цитата выше противоречит моему высказыванию?
skynin писал(а): 2018.07.27, 12:24 перечитываем мой пост о том при соблюдении каких условий оно будет
в противоречивости я и говорю о том что ниже исправлено, но при таком первом таком утвердительном сообщении второе может и неправильно осознаться
skynin писал(а): 2018.07.27, 12:24 перечитываем мой пост о тонкостях реализации драйвера БД.
там же и про "если в запросе передаваемом в each() что-то запрашивать жадно, то оно будет получаться жадно"
что мы запрашиваем - не влияет на то - как сервер БД и драйвер БД обмениваются по протоколу TCP/IP
Продолжаем путать теплое и мягкое, пример:

Код: Выделить всё

        $products = Product::find()->with('mainCategory');
        foreach ($products->each() as $product) {
            echo $product->id;
        }
при таком запросе и наличии 1000 продуктов мы получим 11 запросов в БД, первый к products и 10 к categories (по одному к каждой пачке продуктов)
skynin
Сообщения: 400
Зарегистрирован: 2017.12.12, 10:09

Re: Не работает each

Сообщение skynin »

andku83 писал(а): 2018.07.27, 12:38 про то что там по умолчанию используется 100 забыл дописать (хотя собирался). И не пойму чем цитата выше противоречит моему высказыванию?
объем кружки которой вы собрались вычерпать ванну не равен объему ванной.
batchSize - это объем кружки, а не количество записей который вернул запрос.
за ограничение количества записей отвечает - limit
andku83 писал(а): 2018.07.27, 12:38
skynin писал(а): 2018.07.27, 12:24 там же и про "если в запросе передаваемом в each() что-то запрашивать жадно, то оно будет получаться жадно"
что мы запрашиваем - не влияет на то - как сервер БД и драйвер БД обмениваются по протоколу TCP/IP
Продолжаем путать теплое и мягкое
ну так не путайте :)
лениво - это когда для 1000 продуктов при batchSize =100 будет 10 обращений к серверу БД или буферу драйвера БД (зависит от их реализации)
жадно - это когда для 1000 продуктов будет 1 обращение к серверу БД или буферу драйвера БД

то есть
лениво - это когда мы черпаем кружкой
жадно - когда черпаем другой ванной
andku83 писал(а): 2018.07.27, 12:38

Код: Выделить всё

        $products = Product::find()->with('mainCategory');
        foreach ($products->each() as $product) {
            echo $product->id;
        }
with - это отдельная история.

давайте вначале разберемся с ленивой загрузкой данных с сервера в клиентское приложение и жадной :)
а то продолжаете путать теплое и мягкое - но почему-то мне выговариваете за это.
не путайте! :)
Не желайте странного, и не будет у вас головной боли чтобы достичь этого странного.
Тем более что окажется что оно вам и не нужно было, странное это.
zelenin
Сообщения: 10596
Зарегистрирован: 2013.04.20, 11:30

Re: Не работает each

Сообщение zelenin »

skynin писал(а): 2018.07.27, 11:11 клиент шлет на сервер БД текст запроса.
при этом клиент говорит - ты мне сразу все не отдавай, может мне все и не понадобится.
сервер БД его транслирует и начинает выполнение, выгребая из файлов БД данные
но помня просьбу, выгребает не все, а останавливает выгребание, и отадет порцию
клиент получив порцию что-то с ней делает. и если мало, шлет серверу
дай еще!
сервер БД с места на котором остановился продолжает выгребать из файлов данные.
и опять шлет порцию.

это - в теории.
не совсем так. Клиент (в случае с php это pdo-драйвер) всегда общается с базой в в таком режиме. Это похоже на стрим. Открывается соединение и данные потоком тянутся из базы. В зависимости от параметров запроса pdo может более высокоуровневому клиенту (db-слою yii) отдать либо готовый массив данных либо ресурс данного стрима, чтобы yii сам тянул данные в рамках этого соединения. Так и работает each/batch. Открывается соединение, тянутся данные строка за строкой. Когда строк вытянулось с batchSize, yii полученные данные запихивает в модельки и возвращает вам. При этом соединение не рвется, и данные продолжают тянутся пока не наполнится второй batch. Итд.
skynin писал(а): 2018.07.27, 11:11 сервер БД может не понимать такой опции: "ты мне сразу все не отдавай, может мне все и не понадобится"
сервер всегда работает в режиме стрим. Пачкой или по одному - это уже фича драйвера либо более высокоуровневой надстройкой над драйвером (yii\db, doctrine итд)
skynin
Сообщения: 400
Зарегистрирован: 2017.12.12, 10:09

Re: Не работает each

Сообщение skynin »

zelenin писал(а): 2018.07.27, 13:56 не совсем так.
зашибись, рассказали тоже самое - но "не совсем так" :D
zelenin писал(а): 2018.07.27, 13:56 сервер всегда работает в режиме стрим
мне б вашу уверенность о всех серверах, всей версий, на всех инстансах в мире :D
zelenin писал(а): 2018.07.27, 13:56 это уже фича драйвера либо более высокоуровневой надстройкой над драйвером
то есть, вы уверенно заявляете что ЛЮБОЙ сервер возращает ВСЕ байты результата в ответ на запрос, и только драйвер к серверу - отдает их порциями?
Не желайте странного, и не будет у вас головной боли чтобы достичь этого странного.
Тем более что окажется что оно вам и не нужно было, странное это.
zelenin
Сообщения: 10596
Зарегистрирован: 2013.04.20, 11:30

Re: Не работает each

Сообщение zelenin »

когда следует работать с ресурсом вместо готовой пачки данных? практически всегда. Нам редко бывает нужен в памяти целый батч моделей за раз. Как правило мы каждую модель обрабатываем по одиночке.

Частая ошибка - возвращать массив данных из сервиса/репозитория в поисковым методах.
Пример:
PostRepository::getPosts(string $searchWord, int $limit, int $offset): array.
В данном примере при $limit = 1000 мы получим массив из 1000 моделей и огромное потребление памяти.

Как надо:
PostRepository::getPosts(string $searchWord, int $limit, int $offset): Iterator.
В данном случае мы получаем итератор с инкапсулированным в нем ресурсом, из которого будем по одному тянуть модельки, что-то с ними делая в каждой итерации.

Причем из Iterator'а мы можем получить массив обычным перебором с сохранением в array либо юзая функцию iterator_to_array.
А вот массив из 1000 моделей уже не провернем обратно. Поэтому всегда(!) надо возвращать итератор. (each/batch - это тоже итераторы, чуть более хитрые)
zelenin
Сообщения: 10596
Зарегистрирован: 2013.04.20, 11:30

Re: Не работает each

Сообщение zelenin »

skynin писал(а): 2018.07.27, 14:05то есть, вы уверенно заявляете что ЛЮБОЙ сервер возращает ВСЕ байты результата в ответ на запрос, и только драйвер к серверу - отдает их порциями?
не драйвер отдает порциями, а драйвер принимает порциями.

Открывается tcp/ip соединение, сервер начинает отдавать байты, клиент их принимает.

Вы когда хттп-клиентом страницу скачиваете, у вас клиент принимает данные или сервер пушит данные на клиента?
skynin
Сообщения: 400
Зарегистрирован: 2017.12.12, 10:09

Re: Не работает each

Сообщение skynin »

zelenin писал(а): 2018.07.27, 14:05 когда следует работать с ресурсом вместо готовой пачки данных? практически всегда. Нам редко бывает нужен в памяти целый батч моделей за раз. Как правило мы каждую модель обрабатываем по одиночке.
чем меньше кружка - тем чаще бегать.
чем меньше буфер обмена в любой компьютерной системе - тем выше накладные расходы на обслуживание его наполнения.

то что мы обрабатываем каждую модель друг за дружкой, потому что у нас однопоточный движок php никак не устраняет того что мы обычно обрабатыватаем в итоге - ВСЕ модели.
так какой смысл их запрашивать по одной, с заходом выполнения в глубины драйвера БД, а то и к самой БД, если все аж 50 штук мы получим разом одним all()?
zelenin писал(а): 2018.07.27, 14:05 Частая ошибка - возвращать массив данных из сервиса/репозитория в поисковым методах.
самая частая ошибка - придумывать себе догматы и тупо им следовать.
zelenin писал(а): 2018.07.27, 14:05 Пример:
PostRepository::getPosts(string $searchWord, int $limit, int $offset): array.
В данном примере при $limit = 1000 мы получим массив из 1000 моделей и огромное потребление памяти.
огромное - это сколько? в каком проекте?
что дает в этом проекте - экономия этого потребления?
и зачем запрашивается сразу 1000? вывести всю тысячу пользователю?
zelenin писал(а): 2018.07.27, 14:05 Как надо:
PostRepository::getPosts(string $searchWord, int $limit, int $offset): Iterator.
В данном случае мы получаем итератор с инкапсулированным в нем ресурсом, из которого будем по одному тянуть модельки, что-то с ними делая в каждой итерации.
не пробовал, а как итератор отработает классику:
ArrayHelper::index($posts, 'id')
zelenin писал(а): 2018.07.27, 14:05 Причем из Iterator'а мы можем получить массив обычным перебором с сохранением в array либо юзая функцию iterator_to_array.
и при таком сценарии у нас в памяти будут одновременно висеть все 1000 моделей.
zelenin писал(а): 2018.07.27, 14:05 А вот массив из 1000 моделей уже не провернем обратно.
то есть создание массива из 1000 моделей с помощью итератора это не одно и тоже потребление памяти
чем запросить его с помощью all()
zelenin писал(а): 2018.07.27, 14:05 Поэтому всегда(!) надо возвращать итератор. (each/batch - это тоже итераторы, чуть более хитрые)
эта догма в вашем же примере не спасает от 1000 моделей в памяти одновременно.
Последний раз редактировалось skynin 2018.07.27, 14:39, всего редактировалось 1 раз.
Не желайте странного, и не будет у вас головной боли чтобы достичь этого странного.
Тем более что окажется что оно вам и не нужно было, странное это.
zelenin
Сообщения: 10596
Зарегистрирован: 2013.04.20, 11:30

Re: Не работает each

Сообщение zelenin »

конечно же может быть существует сервер, протокол которого не позволяет верно декодировать байты не зная последнего байта в потоке.
Но уверен что если такие и есть то они выставлены на ретро-выставке "Базы данных 70-х годов".
Обычно протокол описывается четко: видишь байт равный такому-то - началась строка, такой-то байт - сечас будет int, колонка, bool итд, такой-то байт - конец строки. Переходим к следующей.
Последний раз редактировалось zelenin 2018.07.27, 14:24, всего редактировалось 1 раз.
Ответить