Страница 1 из 2
Не работает each
Добавлено: 2018.07.27, 10:00
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 элементов и мне очень критично чтобы выборка производилась блоками, а не все сразу, т.к. данных много.
Re: Не работает each
Добавлено: 2018.07.27, 10:29
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 и убедитесь сами что он
не шлет новые запросы.
Re: Не работает each
Добавлено: 2018.07.27, 10:33
Brainfuck
skynin писал(а): ↑2018.07.27, 10:29
вообще-то each и сделан для того, чтобы вести себя как all.
во-вторых итератор у BatchQueryResult использует PDO fetch, а не как (телепатирую) вы ожидали limit и offset
отройте код метода each и убедитесь сами что он
не шлет новые запросы.
Эмм что? Я почти уверен что раньше он таки слал limit и offset... В таком случае в чем его профит перед all?
Re: Не работает each
Добавлено: 2018.07.27, 10:51
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 - жадная
Re: Не работает each
Добавлено: 2018.07.27, 11:00
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? Или примерно одинаково?
Re: Не работает each
Добавлено: 2018.07.27, 11:11
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() - самый правильный выбор
"фокусы" с порциями между драйвером и сервером БД нам ничего не дадут, и никак не помогут.
Re: Не работает each
Добавлено: 2018.07.27, 11:19
Brainfuck
skynin писал(а): ↑2018.07.27, 11:11
объясняю на пальцах:
клиент шлет на сервер БД текст запроса.
при этом клиент говорит - ты мне сразу все не отдавай, может мне все и не понадобится.
сервер БД его транслирует и начинает выполнение, выгребая из файлов БД данные
но помня просьбу, выгребает не все, а останавливает выгребание, и отадет порцию
клиент получив порцию что-то с ней делает. и если мало, шлет серверу
дай еще!
сервер БД с места на котором остановился продолжает выгребать из файлов данные.
и опять шлет.
это - в теории.
в конкретике нюансы зависят от реализации драйвера к БД и реализации сервера БД.
сервер БД может не понимать такой опции: "ты мне сразу все не отдавай, может мне все и не понадобится"
драйвер БД может запрашивать все, но когда получил часть - уже отдавать их потребителю, при этом в фоне получая остальные данные.
и так и дальше, драйвер БД может отдавать порционно, хотя он получил уже весь ответ.
Хмм, т.е. по сути это те же самые limit/offset, но без sql-запросов? А зачем вообще усложнять? Почему не сделали на limit/offset? Или так быстрее выходит?
Re: Не работает each
Добавлено: 2018.07.27, 11:20
Brainfuck
skynin писал(а): ↑2018.07.27, 11:11
зависит от того как вы работаете с данными. плюс еще и от того что написал выше.
например для гридов в админке - мы точно знаем что
нам нужна только порция данных
и такого-то размера
и для таког-то номера страницы.
значит limit offset и all() - самый правильный выбор
"фокусы" с порциями между драйвером и сервером БД нам ничего не дадут, и никак не помогут.
Я перегоняю данные из одной таблицы в другую. Сейчас кстати открыл для себя batch insert
Re: Не работает each
Добавлено: 2018.07.27, 11:23
skynin
Brainfuck писал(а): ↑2018.07.27, 11:19
Хмм, т.е. по сути это те же самые limit/offset, но без sql-запросов? А зачем вообще усложнять?
по сути да.
а усложнять - потому что происходит большая экономия на ресурсах процессора и памяти в определенных сценариях.
Brainfuck писал(а): ↑2018.07.27, 11:19
Почему не сделали на limit/offset? Или так быстрее выходит?
потому что получается большая экономия на ресурсах процессора и памяти в определенных сценариях.
а вопрос
Почему не сделали на limit/offset?
звучит примерно как
почему системы впрыска топлива в современных автомобилях такие сложные?
почему механического карбюратора недостаточно?
Re: Не работает each
Добавлено: 2018.07.27, 11:42
Brainfuck
Ладно. Кажется все стало ясно. Спасибо что объяснил.
Re: Не работает each
Добавлено: 2018.07.27, 12:20
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() что-то запрашивать жадно, то оно будет получаться жадно, но отдельно для каждой и порций.
Re: Не работает each
Добавлено: 2018.07.27, 12:24
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
Re: Не работает each
Добавлено: 2018.07.27, 12:38
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 (по одному к каждой пачке продуктов)
Re: Не работает each
Добавлено: 2018.07.27, 12:59
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 - это отдельная история.
давайте вначале разберемся с ленивой загрузкой данных с сервера в клиентское приложение и жадной
а то продолжаете путать теплое и мягкое - но почему-то мне выговариваете за это.
не путайте!
Re: Не работает each
Добавлено: 2018.07.27, 13:56
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 итд)
Re: Не работает each
Добавлено: 2018.07.27, 14:05
skynin
zelenin писал(а): ↑2018.07.27, 13:56
не совсем так.
зашибись, рассказали тоже самое - но "не совсем так"
zelenin писал(а): ↑2018.07.27, 13:56
сервер всегда работает в режиме стрим
мне б вашу уверенность о всех серверах, всей версий, на всех инстансах в мире
zelenin писал(а): ↑2018.07.27, 13:56
это уже фича драйвера либо более высокоуровневой надстройкой над драйвером
то есть, вы уверенно заявляете что ЛЮБОЙ сервер возращает ВСЕ байты результата в ответ на запрос, и только драйвер к серверу - отдает их порциями?
Re: Не работает each
Добавлено: 2018.07.27, 14:05
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 - это тоже итераторы, чуть более хитрые)
Re: Не работает each
Добавлено: 2018.07.27, 14:10
zelenin
skynin писал(а): ↑2018.07.27, 14:05то есть, вы уверенно заявляете что ЛЮБОЙ сервер возращает ВСЕ байты результата в ответ на запрос, и только драйвер к серверу - отдает их порциями?
не драйвер отдает порциями, а драйвер принимает порциями.
Открывается tcp/ip соединение, сервер начинает отдавать байты, клиент их принимает.
Вы когда хттп-клиентом страницу скачиваете, у вас клиент принимает данные или сервер пушит данные на клиента?
Re: Не работает each
Добавлено: 2018.07.27, 14:19
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 моделей в памяти одновременно.
Re: Не работает each
Добавлено: 2018.07.27, 14:24
zelenin
конечно же может быть существует сервер, протокол которого не позволяет верно декодировать байты не зная последнего байта в потоке.
Но уверен что если такие и есть то они выставлены на ретро-выставке "Базы данных 70-х годов".
Обычно протокол описывается четко: видишь байт равный такому-то - началась строка, такой-то байт - сечас будет int, колонка, bool итд, такой-то байт - конец строки. Переходим к следующей.