Избегание нескольких ссылок на один и тот же объект в Django ORM - PullRequest
7 голосов
/ 18 января 2012

У нас есть приложение с сильно взаимосвязанными данными, то есть во многих случаях два объекта могут ссылаться на один и тот же объект через отношения. Насколько я могу судить, Django не предпринимает никаких попыток вернуть ссылку на уже извлеченный объект, если вы пытаетесь получить его через другое, ранее не оцененное отношение.

Например:

class Customer( Model ):
    firstName = CharField( max_length = 64 )
    lastName = CharField( max_length = 64 )

class Order( Model ):
    customer = ForeignKey( Customer, related_name = "orders" )

Тогда предположим, что у нас есть один клиент, у которого есть два заказа в БД:

order1, order2 = Order.objects.all()
print order1.customer # (1) One DB fetch here
print order2.customer # (2) Another DB fetch here
print order1.customer == order2.customer # (3) True, because PKs match
print id( order1.customer ) == id( order2.customer ) # (4) False, not the same object

Когда у вас есть сильно взаимосвязанные данные, степень, в которой доступ к отношениям ваших объектов приводит к повторным запросам БД для одних и тех же данных, увеличивается и становится проблемой.

Мы также программируем для iOS, и одна из приятных особенностей CoreData заключается в том, что он поддерживает контекст , так что в данном контексте существует только один экземпляр данной модели. В приведенном выше примере CoreData не выполнила бы вторую выборку в (2), потому что это разрешило бы отношения, используя клиента уже в памяти.

Даже если строка (2) была заменена на ложный пример, предназначенный для принудительного вызова другой выборки из БД (например, print Order.objects.exclude( pk = order1.pk ).get( customer = order1.customer )), CoreData поймет, что результат этой второй выборки разрешен для модели в памяти, и вместо этого вернет существующую модель. нового (т. е. (4) выведет True в CoreData, потому что на самом деле это будет один и тот же объект).

Чтобы застраховаться от такого поведения Django, мы пишем все эти ужасные вещи, чтобы попытаться кэшировать модели в памяти по их (type, pk), а затем проверить отношения с суффиксом _id, чтобы попытаться извлечь их из кэша до того, как вслепую ударяя по БД с другой выборкой. Это сокращает пропускную способность БД, но кажется действительно хрупким и может вызвать проблемы, если нормальный поиск отношений через свойства случайно происходит в некоторой среде contrib или промежуточном программном обеспечении, которое мы не контролируем.

Существуют ли какие-либо передовые практики или структуры для Django, чтобы помочь избежать этой проблемы? Кто-нибудь пытался установить какой-то локальный контекст потока в ORM Django, чтобы избежать повторных поисков и иметь несколько экземпляров в памяти, сопоставляемых с одной и той же моделью БД?

Я знаю, что кеширование запросов, такое как JohnnyCache, существует (и помогает сократить пропускную способность БД), однако по-прежнему существует проблема сопоставления нескольких экземпляров с одной и той же базовой моделью даже при наличии этих показателей.

Ответы [ 2 ]

2 голосов
/ 18 января 2012

Дэвид Крамер django-id-mapper - одна попытка сделать это.

1 голос
/ 19 января 2012

В документации django есть соответствующая страница оптимизации БД ; в основном вызываемые объекты не кэшируются, а атрибуты (последующие вызовы order1.customer не попадают в базу данных), хотя только в контексте их владельца объекта (то есть, не разделяются между различными заказами).

с использованием кэша

Как вы говорите, одним из способов решения вашей проблемы является использование кэша базы данных. Мы используем johnny cache bitbucket, который почти полностью прозрачен; Другой хороший прозрачный - кэш-машина Mozilla . У вас также есть выбор для менее прозрачных систем кэширования, которые на самом деле могут лучше отвечать всем требованиям, см. djangopackages / caching .

Добавление кэша действительно может быть очень полезным, если разные запросы требуют повторного использования одного и того же Клиента; но, пожалуйста, прочитайте это , которое применяется к большинству прозрачных систем кэширования, чтобы продумать, подходит ли ваш шаблон записи / чтения такой системе кэширования.

оптимизация запросов

Другой подход для вашего точного примера - использовать select_related.

order1, order2 = Order.objects.all().select_related('customer')

Таким образом, объект Customer будет загружен сразу в том же SQL-запросе, с небольшими затратами (если это не очень большая запись) и не нужно экспериментировать с другими пакетами.

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...