it-roy-ru.com

В чем разница между select_related и prefetch_related в Django ORM?

В Django doc,

select_related() "следует" за связями внешнего ключа, выбирая дополнительные данные связанного объекта при выполнении своего запроса.

prefetch_related() выполняет отдельный поиск для каждого отношения и выполняет "соединение" в Python.

Что это значит под "объединением в python"? Может кто-нибудь проиллюстрировать примером?

Насколько я понимаю, для связи с внешним ключом используйте select_related; и для связи M2M используйте prefetch_related. Это правильно?

208
NeoWang

Ваше понимание в основном верно. Вы используете select_related, когда объект, который вы собираетесь выбрать, является отдельным объектом, поэтому OneToOneField или ForeignKey. Вы используете prefetch_related, когда собираетесь получить "набор" вещей, поэтому ManyToManyFields, как вы заявили, или наоборот ForeignKeys. Просто чтобы прояснить, что я имею в виду под "обратным ForeignKeys", вот пример:

class ModelA(models.Model):
    pass

class ModelB(models.Model):
    a = ForeignKey(ModelA)

ModelB.objects.select_related('a').all() # Forward ForeignKey relationship
ModelA.objects.prefetch_related('modelb_set').all() # Reverse ForeignKey relationship

Разница в том, что select_related выполняет SQL-соединение и поэтому возвращает результаты в виде части таблицы с SQL-сервера. prefetch_related, с другой стороны, выполняет другой запрос и, следовательно, уменьшает избыточные столбцы в исходном объекте (ModelA в приведенном выше примере). Вы можете использовать prefetch_related для всего, для чего вы можете использовать select_related.

Компромисс состоит в том, что prefetch_related должен создать и отправить список идентификаторов для выбора обратно на сервер, это может занять некоторое время. Я не уверен, есть ли хороший способ сделать это в транзакции, но я понимаю, что Django всегда просто отправляет список и говорит SELECT ... WHERE pk IN (..., .. .,...) в принципе. В этом случае, если предварительно выбранные данные редки (скажем, объекты состояния США, связанные с адресами людей), это может быть очень хорошо, однако, если они ближе к однозначному, это может привести к потере большого количества сообщений. Если есть сомнения, попробуйте оба варианта и посмотрите, какие из них работают лучше.

Все, что обсуждалось выше, в основном касается связи с базой данных. Однако на стороне Python prefetch_related имеет дополнительное преимущество, заключающееся в том, что один объект используется для представления каждого объекта в базе данных. С select_related дубликаты будут создаваться в Python для каждого "родительского" объекта. Так как объекты в Python имеют приличную долю памяти, это также может быть рассмотрено.

327
CrazyCasta

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

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

select_related выполняет соединение с каждым поиском, но расширяет выбор, чтобы включить столбцы всех соединенных таблиц. Однако у этого подхода есть одна оговорка.

Объединения могут умножить количество строк в запросе. Когда вы выполняете соединение по внешнему ключу или однозначному полю, количество строк не увеличивается. Однако объединения "многие ко многим" не имеют такой гарантии. Таким образом, Django ограничивает select_related отношениями, которые не могут неожиданно привести к массовому объединению.

"join in python" для prefetch_related немного тревожнее, чем должно быть. Он создает отдельный запрос для каждой таблицы, к которой нужно присоединиться. Он фильтрует каждую из этих таблиц с предложением WHERE IN, например:

SELECT "credential"."id",
       "credential"."uuid",
       "credential"."identity_id"
FROM   "credential"
WHERE  "credential"."identity_id" IN
    (84706, 48746, 871441, 84713, 76492, 84621, 51472);

Вместо того, чтобы выполнять одно соединение с потенциально слишком большим количеством строк, каждая таблица разбивается на отдельный запрос.

13
cdosborn