Ну, как-то странно уже получить из базы 1000 записей, а вернуть клиенту только 25 из них - зачем тогда сразу читать в разы больше? А если клиент хотел всего 25 - зачем мы стали столько читать?
Правильно, sale_id - уникальный ключ (поскольку PK), поэтому (sale_dt, sale_id) - тоже уникальный ключ, который позволяет вычитывать блок записей, без оглядки на сайд-эффекты возможной неуникальности ключа.
Потому что никто не гарантирует монотонного возрастания sale_dt вместе с sale_id. Наоборот, в учетных системах регулярно возникает задача отразить что-то "задним числом", завести документ за прошлую дату.
Как составной индекс поможет при фильтрации по полю из связанной таблицы конкретно в приведенном примере?
Как правило, в подобных системах таблицы не суперактивны - это не логи все-таки, а результаты каких-то пользовательских действий, которых не может быть "бесконечно много".
Сотни тысяч / миллионы записей в таких таблицах обычно. Если бизнес крупный и существующий долго - сотни миллионов, но все равно единицы TPS.
"Не пропускает пуши с eval" - это такая же паранойя, как и "goto - зло". То есть грамотный разработчик хоть на лету файл сгенерирует и сделает require, но это вынуждает его искать лишние лазейки.
Да, "тривиально" запушить eval должно быть сложно для большинства разработчиков, но для "грамотных" должна быть возможность сделать именно это, а не заниматься прокладкой бэкдоров.
Речь не об "архивных", а о тех, которые находятся "в проработке" - например, загрузили мы с разных "желтых страниц" кучу клиентов для прозвона...
Конечно, тут можно начать вводить флаги на записи клиента "были заказы", "взят в работу", "выставлено КП", ... - но это имеет косвенное отношение к задаче, потому что условия отбора заказов могут оказаться нефиксированными.
Тут нас может поджидать проблема, если в clients лежат не только "активные" клиенты, уже делавшие заказы, но и все потенциальные, коих может быть и миллион - а такой запрос масштабируется линейно именно по ним.
В целом - так. У нас 92 страницы индекса - их мы вычитаем каждую однократно. В таблице - 521, но каждую из них мы читаем многократно, но ровно одну для каждой записи из индекса.
Я так понимаю по WindowAgg / Run Condition, что план из PG15, а на более ранних версиях было бы еще дольше. Хотя сравнивать при дисковой сортировке, съевшей 37мс не особо корректно.
Seq Scan настолько быстр, поскольку читает всю относительно небольшую таблицу как раз последовательно, а вот Index Scan после прочтения каждой записи индекса вынужден прочитать запись heap таблицы - ровно против этого придумали Index Only Scan.
Причем чтение с диска и pagecache тут у нас не играли, поскольку везде только shared hit - то есть было чтение страницы только из памяти (иначе было бы shared read).
Вот тут я рассказываю подробнее, как разные методы доступа работают.
Ну, как-то странно уже получить из базы 1000 записей, а вернуть клиенту только 25 из них - зачем тогда сразу читать в разы больше? А если клиент хотел всего 25 - зачем мы стали столько читать?
"Последовательный перебор" - это как, через JOIN?
Правильно,
sale_id- уникальный ключ (поскольку PK), поэтому(sale_dt, sale_id)- тоже уникальный ключ, который позволяет вычитывать блок записей, без оглядки на сайд-эффекты возможной неуникальности ключа.Потому что никто не гарантирует монотонного возрастания
sale_dtвместе сsale_id. Наоборот, в учетных системах регулярно возникает задача отразить что-то "задним числом", завести документ за прошлую дату.Как составной индекс поможет при фильтрации по полю из связанной таблицы конкретно в приведенном примере?
Как правило, в подобных системах таблицы не суперактивны - это не логи все-таки, а результаты каких-то пользовательских действий, которых не может быть "бесконечно много".
Сотни тысяч / миллионы записей в таких таблицах обычно. Если бизнес крупный и существующий долго - сотни миллионов, но все равно единицы TPS.
64K - 51ms, 1M - 437ms, 8M - 3458ms - везде не лучший результат
Полный код
Ну, в случае indirect call он доступа туда не имеет.
return new Function('obj', res.join(','));У меня получились ровно те же результаты, что и для
eval."Не пропускает пуши с eval" - это такая же паранойя, как и "goto - зло". То есть грамотный разработчик хоть на лету файл сгенерирует и сделает
require, но это вынуждает его искать лишние лазейки.Да, "тривиально" запушить
evalдолжно быть сложно для большинства разработчиков, но для "грамотных" должна быть возможность сделать именно это, а не заниматься прокладкой бэкдоров.А в чем для транслятора должна быть принципиальная разница между текстом функции из файла и текстом, сгенерированным в рантайме?
Если немного модифицировать, то можно, да, хоть это и не совсем фильтрация:
Но лучше так не делать, поскольку проигрывает всем остальным вариантам в разы:
А как
first_valueпоможет отфильтровать остальные записи по ключу?Речь не об "архивных", а о тех, которые находятся "в проработке" - например, загрузили мы с разных "желтых страниц" кучу клиентов для прозвона...
Конечно, тут можно начать вводить флаги на записи клиента "были заказы", "взят в работу", "выставлено КП", ... - но это имеет косвенное отношение к задаче, потому что условия отбора заказов могут оказаться нефиксированными.
Тут нас может поджидать проблема, если в
clientsлежат не только "активные" клиенты, уже делавшие заказы, но и все потенциальные, коих может быть и миллион - а такой запрос масштабируется линейно именно по ним.Посмотрите все серии статей от @erogov, там много полезного найдется.
Вот join-узлы обеспечивают только способ соединения самих записей, а чтение для них осуществляют нижележащие
Scan-узлы.В целом - так. У нас 92 страницы индекса - их мы вычитаем каждую однократно. В таблице - 521, но каждую из них мы читаем многократно, но ровно одну для каждой записи из индекса.
В моем профиле есть ссылки на статьи о многих способах оптимизаций для PG, и в рассылке Planet PostgreSQL интересное встречается.
Ага, индекс и
Incremental Sortв памяти помогли, но все равно не лучшеJOIN.Даже если предположить, что при увеличении work_mem время на сортировку обнулится, все равно останется еще 15мс.
Я так понимаю по
WindowAgg / Run Condition, что план из PG15, а на более ранних версиях было бы еще дольше. Хотя сравнивать при дисковой сортировке, съевшей 37мс не особо корректно.Seq Scanнастолько быстр, поскольку читает всю относительно небольшую таблицу как раз последовательно, а вотIndex Scanпосле прочтения каждой записи индекса вынужден прочитать запись heap таблицы - ровно против этого придумалиIndex Only Scan.Причем чтение с диска и pagecache тут у нас не играли, поскольку везде только
shared hit- то есть было чтение страницы только из памяти (иначе было быshared read).Вот тут я рассказываю подробнее, как разные методы доступа работают.