#ruby #activerecord #ruby-on-rails-5 #query-optimization
#ruby #activerecord #ruby-on-rails-5 #оптимизация запросов
Вопрос:
В настоящее время я пытаюсь минимизировать количество SQL-запросов в приложении rails. И использовал область для фильтрации записей и обнаружил, что в моем журнале сервера область запускает SQL-запрос, хотя он показывает КЭШ (0,00 мс), даже если он фильтрует запись с использованием атрибутов записи. (например. фильтрация с использованием obj.status)
Я попытался использовать метод класса в модели, снова, когда я когда-либо вызываю
def pdf_files
all.select(amp;:pdf?)
end
области,
scope :pdf_files, -> { all.select(amp;:pdf?) }
это запускает sql-запрос.
Я попытался переопределить ActiveRecord ::Associations::CollectionProxy, чтобы включить этот метод, например,
class ActiveRecord::Associations::CollectionProxy
def pdf_files
to_a.select(amp;:pdf?)
end
end
Сработало, но этот метод также может быть вызван из другой модели.
Я попытался использовать блоки, чтобы расширить ваши ассоциации с помощью дополнительных методов, например.
has_many :files, do
def pdf_files
to_a.select(amp;:pdf?)
end
end
Это работает для меня, он выбирает файлы без какого-либо запроса sql.
Но дело в том, что. Мне нужны эти методы, доступные только для файловой модели, поскольку эти методы требуются для всех других моделей, связанных с файловой моделью, и я не хочу нарушать соглашение rails, например, @email.files.pdf .
Также обнаружено, что в консоли rails, когда я набираю File::ProxyCollection, он выдает мне ActiveRecord ::Associations::CollectionProxy. Но не знаю, как добавить к нему метод.
Я хочу иметь возможность вызывать @email.files.pdf?, @email.files.doc ?, чтобы отфильтровать результаты без выполнения какого-либо запроса вообще, даже если он вызывает SQL-запрос КЭША.
Определения моделей
class TestFile < ApplicationRecord
belongs_to :fileable, polymorphic: true, optional: true
scope :document_files, -> { all.select(amp;:document?) }
scope :ebook_files, -> { all.select(amp;:ebook?) }
enum file_type: [:document, :ebook, :paper, :article, :picture]
end
class TestEmail < ApplicationRecord
has_many :test_files, as: :fileable
end
class TestPost < ApplicationRecord
has_many :test_files, as: :fileable
end
В консоли
email = TestEmail.includes(:test_files).first
document_files = email.test_files.document_files
Запрос, выполняемый для области document_files, равен
Но если я сделаю что-то вроде,
class TestFile < ApplicationRecord
belongs_to :fileable, polymorphic: true, optional: true
enum file_type: [:document, :ebook, :paper, :article, :picture]
end
class TestEmail < ApplicationRecord
has_many :test_files, as: :fileable do
def document_files
to_a.select(amp;:document?)
end
end
end
class TestPost < ApplicationRecord
has_many :test_files, as: :fileable do
def document_files
to_a.select(amp;:document?)
end
end
end
Комментарии:
1. Честно говоря, вы пытаетесь сделать что-то очень странное. Не могли бы вы предоставить определения модели (с ассоциацией) и пример запроса, для которого вы хотите избавиться от ненужных запросов?
2. @KonstantinStrukov Я отредактировал свой вопрос, включив в него определения моделей и снимок экрана, показывающий состояние sql-запроса для каждого случая. Это то, что вы ищете?
Ответ №1:
Итак, у вас есть полиморфная ассоциация, и вы хотите получить связанные данные с минимально возможными запросами, верно?
Что касается меня, сама задача («минимизировать количество запросов») имеет смысл только с разумными ограничениями. Всегда полезно избавиться от N 1, но не обязательно пытаться выполнить все в 1 запросе или что-то в этом роде — сложный запрос может быть медленнее, чем несколько последующих тривиальных. Кроме того, сложные сложные запросы, как правило, подвержены ошибкам.
Теперь давайте вернемся к вашему коду. Прежде всего, эта идея
scope :document_files, -> { all.select(amp;:document?) }
выглядит довольно плохо для меня по двум причинам:
-
все загружается в память, и в случае большой таблицы это вредит. Иногда это необходимый компромисс, но не в этом случае (вам не нужна какая-либо тяжелая обработка и т. Д. — Просто фильтрация данных)
-
эта «область» не является обычной — ее вызов дает вам
Array
вместо отношения AR, поэтому ее нельзя связать, как это делают надлежащие области AR и т. Д. Это просто вводящий в заблуждение код, который пахнет. Просто попробуйте что-нибудь. нравитсяTestFile.document_files.where(<some_extra_conditions>)
, и вы получитеNoMethodError
— вероятно, не тот результат, который можно было ожидать от простого и понятного кода…
Использование расширений ассоциации также бесполезно в вашем случае — это делает ваш код более загадочным, но на самом деле не дает вам никаких преимуществ. Вы ошибаетесь, предполагая, что он работает каким-то особым образом — фактически, он выполняет точно такую же работу, как если бы вы явно вызывали to_a.select...
прокси-сервер ассоциации.
Я бы предложил что-то более простое и идиоматичное:
class TestFile < ApplicationRecord
belongs_to :fileable, polymorphic: true, optional: true
scope :document_files, -> { where(file_type: :document) }
scope :ebook_files, -> { where(file_type: :ebook) }
enum file_type: [:document, :ebook, :paper, :article, :picture]
end
...
Затем, используя предварительную загрузку, где это уместно, вы можете получить небольшое количество запросов (без N 1).
Хотите оптимизировать это еще больше? Что ж, есть много способов сделать это, используя более низкоуровневый API: например, вы можете использовать connection#select_all
с произвольно сложным запросом, а затем создавать экземпляры необходимых записей вручную ActiveRecord::Result
без дополнительных запросов… Но AR — довольно упрямый фреймворк ORM, и если вы собираетесь «бороться» с ним, вы, скорее всего, довольно быстро получите грязный и не поддерживаемый код.
Если вам действительно нужно что-то более гибкое, я бы посоветовал попробовать другие варианты (продолжение, ROM)…
Комментарии:
1. Я уже пробовал эту опцию тоже, для области document_files , -> { where(file_type: :document) } также запускает SQL-запрос, которого я хочу избежать, поскольку у нас уже есть необходимые тестовые файлы, которые уже загружены, и нам просто нужно отфильтровать требуемые файлы из этих загруженных записей.
2. И каждый раз, когда я делаю что-то вроде documents = @emal.test_files.document_files , каждый раз, когда он выполняет новый SQL-запрос
3. Просто подумайте, как насчет использования вспомогательных методов для обработки немодельной логики