.where в Ruby против обнаружения

Я ищу метод, который быстрее и использует меньше серверной обработки. В моем приложении я могу использовать как .where, так и .detect:

Где:

User.where(id: 1)
# User Load (0.5ms)

Обнаружение:

User.all.detect{ |u| u.id == 1 }
# User Load (0.7ms). Sometimes increases more than .where

Я понимаю, что .detect возвращает первый элемент в списке, для которого блок возвращает TRUE, но как это сравнивается с .where, если у меня тысячи пользователей?

Отредактировано для ясности.

.where используется в этом примере, потому что я не могу запрашивать только id. Что делать, если у меня есть столбец таблицы с именем «имя»?


person Sylar    schedule 07.09.2016    source источник


Ответы (3)


В этом примере

User.find(1)        # or
User.find_by(id: 1)

будут самые быстрые решения. Поскольку оба запроса сообщают базе данных, что нужно вернуть ровно одну запись с совпадающим id. Как только база данных находит совпадающую запись, она не смотрит дальше, а немедленно возвращает эту запись.

Тогда как

User.where(id: 1)

вернет массив объектов, соответствующих условию. Это означает: после того, как совпадающая запись будет найдена, база данных продолжит поиск других записей, соответствующих запросу, и поэтому всегда сканирует всю таблицу базы данных. В этом случае — поскольку id, скорее всего, является столбцом с уникальными значениями — он вернет массив только с одним экземпляром.

В отличие от

User.all.detect { |u| u.id == 1 }

что бы загрузить всех пользователей из базы данных. Это приведет к загрузке тысяч пользователей в память, созданию экземпляров ActiveRecord, перебору этого массива и удалению всех записей, не соответствующих условию. Это будет очень медленно по сравнению с простой загрузкой соответствующих записей из базы данных.

Системы управления базами данных оптимизированы для выполнения запросов выбора, и вы можете улучшить их возможности, разработав полезную схему и добавив соответствующие индексы. Каждая запись, загружаемая из базы данных, должна быть переведена в экземпляр ActiveRecord и потреблять память — обе операции не бесплатны. Поэтому эмпирическое правило должно быть таким: по возможности запускайте запросы непосредственно в базе данных, а не в Ruby.

person spickermann    schedule 07.09.2016
comment
Я полагаю, что технически User.where(id: 1) возвращает прокси-сервер коллекции ActiveRecord? Но обратите внимание на то, что find является правильным методом. - person David Aldridge; 07.09.2016
comment
@DavidAldridge: вы правы, where возвращает отношение ActiveRecord (а не массив). В этом примере это не имеет большого значения, но в других примерах может быть важно быть более точным. - person spickermann; 07.09.2016
comment
Так что, если это не просто id я после? Вот почему я использую .where в примере. - person Sylar; 07.09.2016
comment
@Sylar: версия find работает только при использовании первичного ключа. find_by следует использовать, когда вас интересует только одна (первая совпавшая) запись (find_by — это сокращение от where(...).first). where всегда будет быстрее, чем all.detect для нескольких записей. Не забудьте добавить индекс, когда вы регулярно запрашиваете таблицы по столбцам, отличным от id. - person spickermann; 07.09.2016
comment
Отлично. Значит, find_by для электронной почты будет быстрее, чем where? Электронная почта будет уникальной. - person Sylar; 07.09.2016
comment
Да, find_by(email: ...) будет самым быстрым решением, когда электронные письма уникальны в вашей базе данных. Обязательно добавьте уникальный индекс в столбец базы данных и не полагайтесь только на проверки уникальности Rails. - person spickermann; 07.09.2016

Примечание В этом конкретном случае следует использовать ActiveRecord#find, вместо этого обратитесь к ответу @spickermann.

User.where выполняется на уровне БД, возвращая одну запись.

User.all.detect вернет все записи в приложение, и только затем пройдёт через на уровне ruby.

Тем не менее, должен использовать where. Первый устойчив к количеству записей, их могут быть миллиарды, а время выполнения / потребление памяти будут почти одинаковыми (O(1)). Второй может даже не работать с миллиардами записей.

person Aleksei Matiushkin    schedule 07.09.2016
comment
Как говорит Спикерманн, where возвращает набор записей. Это может быть ноль, один или много. Find — правильный метод ActiveRecord. - person David Aldridge; 07.09.2016
comment
В начале вашего ответа жирным шрифтом выделено NB. Что это обозначает? - person Tass; 07.09.2016

Вот общее руководство:

Используйте .find(id) всякий раз, когда вы ищете уникальную запись. Вы можете использовать что-то вроде .find_by_email(email) или .find_by_name(name) или подобное (эти методы поиска генерируются автоматически) при поиске полей без идентификатора, если есть только одна запись с этим конкретным значением.

Используйте .where(...).limit(1), если ваш запрос слишком сложен для запроса .find_by или вам нужно использовать упорядочение, но вы все еще уверены, что хотите вернуть только одну запись.

Используйте .where(...) при получении нескольких записей.

Используйте .detect только в том случае, если вы не можете этого избежать. Типичными случаями использования .detect являются перечисления, отличные от ActiveRecord, или когда у вас есть набор записей, но вы не можете написать условие соответствия в SQL (например, если оно включает сложную функцию). Поскольку .detect является самым медленным, убедитесь, что перед вызовом .detect вы использовали SQL для максимально возможного сокращения запроса. То же самое для .any? и других перечисляемых методов. То, что они доступны для объектов ActiveRecord, не означает, что их стоит использовать;)

person Sprachprofi    schedule 21.11.2019
comment
Привет и спасибо за вашу поддержку. Этот вопрос был 3 года назад. Вы уверены, что find_by_<x> был реализован именно тогда? - person Sylar; 21.11.2019