Базы данных
Это руководство описывает поддержку Django для взаимодействия с несколькими базами данных. Большая часть остальной документации Django предполагает взаимодействие с одной базой данных. Если вам нужно взаимодействовать с несколькими базами данных, вам нужно выполнить несколько дополнительных шагов.
Определение баз данных
Первый шаг для использования более чем одной базы данных с Django — сообщить Django о серверах баз данных, которые вы будете использовать. Это делается с помощью настройки DATABASES. Эта настройка сопоставляет псевдонимы баз данных, которые являются способом ссылки на определенную базу данных в Django, со словарем настроек для этого конкретного подключения. Настройки во внутренних словарях подробно описаны в документации DATABASES.
Базы данных могут иметь любой выбранный вами псевдоним. Однако псевдоним default имеет особое значение. Django использует базу данных с псевдонимом default при отсутствии выбора другой базы данных.
Следующий пример settings.py фрагмент определяет две базы данных — базу данных PostgreSQL по умолчанию и базу данных MySQL под названием users:
DATABASES = {
'default': {
'NAME': 'app_data',
'ENGINE': 'django.db.backends.postgresql_psycopg2',
'USER': 'postgres_user',
'PASSWORD': 's3krit'
},
'users': {
'NAME': 'user_data',
'ENGINE': 'django.db.backends.mysql',
'USER': 'mysql_user',
'PASSWORD': 'priv4te'
}
}
Если концепция базы данных по умолчанию не подходит для вашего проекта, вам нужно тщательно указывать используемую базу данных. Django требует определения записи базы данных по умолчанию, но словарь параметров можно оставить пустым, если он не будет использоваться. Вам необходимо настроить DATABASE_ROUTERS для всех моделей ваших приложений, включая приложения contrib и сторонних разработчиков, которые вы используете, чтобы никакие запросы не направлялись в базу данных по умолчанию. Ниже приведен пример settings.py фрагмент, определяющий две базы данных, отличные от базы данных по умолчанию, при этом запись default намеренно оставлена пустой:
DATABASES = {
'default': {},
'users': {
'NAME': 'user_data',
'ENGINE': 'django.db.backends.mysql',
'USER': 'mysql_user',
'PASSWORD': 'superS3cret'
},
'customers': {
'NAME': 'customer_data',
'ENGINE': 'django.db.backends.mysql',
'USER': 'mysql_cust',
'PASSWORD': 'veryPriv@ate'
}
}
Если вы попытаетесь получить доступ к базе данных, не определенной в настройке DATABASES, Django выведет исключение django.db.utils.ConnectionDoesNotExist.
Синхронизация баз данных
Команда управления migrate работает с одной базой данных за раз. По умолчанию она работает с базой данных default, но с помощью аргумента --database вы можете указать migrate синхронизировать другую базу данных. Таким образом, для синхронизации всех моделей со всеми базами данных в нашем примере необходимо вызвать:
$ ./manage.py migrate $ ./manage.py migrate --database=users
Если вы не хотите, чтобы каждое приложение синхронизировалось с определенной базой данных, вы можете определить маршрутизатор баз данных маршрутизатор баз данных, который реализует политику, ограничивающую доступность конкретных моделей.
Использование других команд управления
Другие django-admin команды, взаимодействующие с базой данных, работают аналогично migrate — они всегда работают с одной базой данных за раз, используя --database для управления используемой базой данных.
Автоматическая маршрутизация баз данных
Самый простой способ использовать несколько баз данных — настроить схему маршрутизации баз данных. Схема маршрутизации по умолчанию гарантирует, что объекты остаются «привязанными» к своей исходной базе данных (т.е., объект, извлеченный из базы данных foo, будет сохранен в той же базе данных). Схема маршрутизации по умолчанию гарантирует, что если база данных не указана, все запросы возвращаются к базе данных default.
Вам ничего не нужно делать, чтобы активировать схему маршрутизации по умолчанию — она предоставляется «из коробки» в каждом проекте Django. Однако, если вы хотите реализовать более интересные методы распределения баз данных, вы можете определить и установить свои собственные маршрутизаторы баз данных.
Маршрутизаторы баз данных
Маршрутизатор баз данных — это класс, который предоставляет до четырех методов:
-
db_for_read(model, **hints) -
Предлагает базу данных, которая должна использоваться для операций чтения для объектов типа
model.Если операция базы данных может предоставить дополнительную информацию, которая может помочь в выборе базы данных, она будет предоставлена в словаре
hints. Подробная информация о допустимых подсказках предоставляется ниже.Возвращает
Noneесли нет предложения.
-
db_for_write(model, **hints) -
Предлагает базу данных, которая должна использоваться для записи объектов типа Модель.
Если операция базы данных может предоставить дополнительную информацию, которая может помочь в выборе базы данных, она будет предоставлена в словаре
hints. Подробная информация о допустимых подсказках предоставляется ниже.Возвращает
Noneесли нет предложения.
-
allow_relation(obj1, obj2, **hints) -
Возвращает
Trueесли отношение междуobj1иobj2должно быть разрешено,Falseесли отношение должно быть предотвращено илиNoneесли у маршрутизатора нет мнения. Это чисто операция проверки, используемая операциями с внешними ключами и множественным к множественному, чтобы определить, разрешено ли отношение между двумя объектами.
-
allow_migrate(db, app_label, model_name=None, **hints) -
Определяет, разрешена ли операция миграции в базе данных с псевдонимом
db. ВозвращаетTrueесли операция должна быть выполнена,Falseесли она не должна выполняться, илиNoneесли у маршрутизатора нет мнения.Позиционный аргумент
app_label— метка мигрируемого приложения.model_nameустанавливается большинством операций миграции в значениеmodel._meta.model_name(строчная версия модели__name__) мигрируемой модели. Его значение —Noneдля операцийRunPythonиRunSQL, если они не предоставят его с помощью подсказок.hintsиспользуются некоторыми операциями для передачи дополнительной информации маршрутизатору.Когда
model_nameустановлено,hintsобычно содержит класс модели по ключу'model'. Обратите внимание, что это может быть историческая модель, и, следовательно, не иметь пользовательских атрибутов, методов или менеджеров. Вы должны полагаться только на_meta.Этот метод также можно использовать для определения доступности модели в данной базе данных.
Обратите внимание, что миграции просто молча не будут выполнять никаких операций с моделью, для которой это возвращает
False. Это может привести к поврежденным внешним ключам, дополнительным таблицам или отсутствующим таблицам, если вы измените его после применения некоторых миграций.Подпись
allow_migrateзначительно изменилась по сравнению с предыдущими версиями. См. заметки о устаревании для получения дополнительной информации.
Маршрутизатор не обязан предоставлять все эти методы — он может опустить один или несколько из них. Если один из методов опущен, Django пропустит этот маршрутизатор при выполнении соответствующей проверки.
Подсказки
Подсказки, полученные маршрутизатором базы данных, могут быть использованы для определения базы данных, которая должна получить данный запрос.
В настоящее время единственной подсказкой, которая будет предоставлена, является instance, экземпляр объекта, который относится к операции чтения или записи, которая выполняется. Это может быть экземпляр, который сохраняется, или это может быть экземпляр, который добавляется в отношении «многие ко многим». В некоторых случаях подсказка экземпляра вообще не будет предоставлена. Маршрутизатор проверяет существование подсказки экземпляра и определяет, должна ли эта подсказка использоваться для изменения поведения маршрутизации.
Использование маршрутизаторов
Маршрутизаторы баз данных устанавливаются с помощью настройки DATABASE_ROUTERS. Эта настройка определяет список имен классов, каждый из которых определяет маршрутизатор, который должен использоваться главным маршрутизатором (django.db.router).
Главный маршрутизатор используется операциями базы данных Django для распределения использования базы данных. Всякий раз, когда запрос должен узнать, какую базу данных использовать, он вызывает главный маршрутизатор, предоставив модель и подсказку (если доступна). Затем Django пытается каждый маршрутизатор по очереди, пока не будет найдено предложение базы данных. Если предложение не найдено, он пытается использовать текущий _state.db подсказки экземпляра. Если подсказка экземпляра не была предоставлена или у экземпляра нет текущего состояния базы данных, главный маршрутизатор назначит базу данных default.
Пример
Пример только для демонстрации!
Этот пример предназначен для демонстрации того, как можно использовать инфраструктуру маршрутизатора для изменения использования базы данных. Он намеренно игнорирует некоторые сложные вопросы, чтобы продемонстрировать, как используются маршрутизаторы.
Этот пример не будет работать, если какие-либо модели в myapp содержат отношения к моделям за пределами базы данных other. Взаимоотношения между базами данных вводят проблемы целостности ссылок, с которыми Django в настоящее время не справляется.
Также некорректно описываемая конфигурация «мастер/слабей» (именуемая мастером/рабочим сервером в некоторых базах данных) — она не предоставляет никакого решения для обработки задержек репликации (т. е. противоречий в запросах, возникающих из-за времени, затрачиваемого на распространение записи до реплик). Она также не учитывает взаимодействие транзакций со стратегией использования базы данных.
Итак, что это означает на практике? Рассмотрим другой пример конфигурации. В ней будет несколько баз данных: одна для приложения auth, а все остальные приложения используют схему мастер-слаб с двумя репликами для чтения. Вот настройки, определяющие эти базы данных:
DATABASES = {
'default': {},
'auth_db': {
'NAME': 'auth_db',
'ENGINE': 'django.db.backends.mysql',
'USER': 'mysql_user',
'PASSWORD': 'swordfish',
},
'primary': {
'NAME': 'primary',
'ENGINE': 'django.db.backends.mysql',
'USER': 'mysql_user',
'PASSWORD': 'spam',
},
'replica1': {
'NAME': 'replica1',
'ENGINE': 'django.db.backends.mysql',
'USER': 'mysql_user',
'PASSWORD': 'eggs',
},
'replica2': {
'NAME': 'replica2',
'ENGINE': 'django.db.backends.mysql',
'USER': 'mysql_user',
'PASSWORD': 'bacon',
},
}
Теперь нам нужно обработать маршрутизацию. Сначала нам нужен маршрутизатор, который знает, как отправлять запросы для приложения auth на auth_db:
class AuthRouter(object):
"""
A router to control all database operations on models in the
auth application.
"""
def db_for_read(self, model, **hints):
"""
Attempts to read auth models go to auth_db.
"""
if model._meta.app_label == 'auth':
return 'auth_db'
return None
def db_for_write(self, model, **hints):
"""
Attempts to write auth models go to auth_db.
"""
if model._meta.app_label == 'auth':
return 'auth_db'
return None
def allow_relation(self, obj1, obj2, **hints):
"""
Allow relations if a model in the auth app is involved.
"""
if obj1._meta.app_label == 'auth' or \
obj2._meta.app_label == 'auth':
return True
return None
def allow_migrate(self, db, app_label, model_name=None, **hints):
"""
Make sure the auth app only appears in the 'auth_db'
database.
"""
if app_label == 'auth':
return db == 'auth_db'
return None
А также нам нужен маршрутизатор, который отправляет все остальные приложения в конфигурацию мастер-реплики и случайным образом выбирает реплику для чтения:
import random
class PrimaryReplicaRouter(object):
def db_for_read(self, model, **hints):
"""
Reads go to a randomly-chosen replica.
"""
return random.choice(['replica1', 'replica2'])
def db_for_write(self, model, **hints):
"""
Writes always go to primary.
"""
return 'primary'
def allow_relation(self, obj1, obj2, **hints):
"""
Relations between objects are allowed if both objects are
in the primary/replica pool.
"""
db_list = ('primary', 'replica1', 'replica2')
if obj1._state.db in db_list and obj2._state.db in db_list:
return True
return None
def allow_migrate(self, db, app_label, model_name=None, **hints):
"""
All non-auth models end up in this pool.
"""
return True
Наконец, в файле настроек мы добавим следующее (заменив path.to. фактическим путем Python к модулю(ям), где определены маршрутизаторы):
DATABASE_ROUTERS = ['path.to.AuthRouter', 'path.to.PrimaryReplicaRouter']
Порядок обработки маршрутизаторов важен. Маршрутизаторы будут запрошены в порядке их перечисления в настройке DATABASE_ROUTERS. В этом примере AuthRouter обрабатывается перед PrimaryReplicaRouter, и в результате решения относительно моделей в auth обрабатываются до принятия любых других решений. Если в настройке DATABASE_ROUTERS два маршрутизатора были перечислены в другом порядке, PrimaryReplicaRouter.allow_migrate() был бы обработан первым. Необходимость обработки всех моделей в конфигурации мастер-реплики означает, что все модели будут доступны на всех базах данных.
После установки этой настройки запустим некоторый код Django:
>>> # This retrieval will be performed on the 'auth_db' database >>> fred = User.objects.get(username='fred') >>> fred.first_name = 'Frederick' >>> # This save will also be directed to 'auth_db' >>> fred.save() >>> # These retrieval will be randomly allocated to a replica database >>> dna = Person.objects.get(name='Douglas Adams') >>> # A new object has no database allocation when created >>> mh = Book(title='Mostly Harmless') >>> # This assignment will consult the router, and set mh onto >>> # the same database as the author object >>> mh.author = dna >>> # This save will force the 'mh' instance onto the primary database... >>> mh.save() >>> # ... but if we re-retrieve the object, it will come back on a replica >>> mh = Book.objects.get(title='Mostly Harmless')
Ручное выбор базы данных
Django также предоставляет API, которое позволяет вам сохранять полный контроль над использованием базы данных в вашем коде. Ручное задание базы данных будет иметь приоритет над базой данных, назначенной маршрутизатором.
Ручное выбор базы данных для QuerySet
Вы можете выбрать базу данных для QuerySet в любой момент в цепочке QuerySet. Просто вызовите using() на QuerySet для получения другого QuerySet, который использует указанную базу данных.
using() принимает один аргумент: псевдоним базы данных, на которой вы хотите выполнить запрос. Например:
>>> # This will run on the 'default' database.
>>> Author.objects.all()
>>> # So will this.
>>> Author.objects.using('default').all()
>>> # This will run on the 'other' database.
>>> Author.objects.using('other').all()
Выбор базы данных для save()
Используйте ключевое слово using для Model.save() для указания базы данных, в которую должны быть сохранены данные.
Например, чтобы сохранить объект в базу данных legacy_users, вы бы использовали это:
>>> my_object.save(using='legacy_users')
Если вы не укажете using, метод save() сохранит данные в базу данных по умолчанию, назначенную маршрутизаторами.
Перемещение объекта из одной базы данных в другую
Если вы сохранили экземпляр в одну базу данных, может возникнуть желание использовать save(using=...) в качестве способа миграции экземпляра в новую базу данных. Однако, если вы не предпримите соответствующих шагов, это может привести к непредвиденным последствиям.
Рассмотрим следующий пример:
>>> p = Person(name='Fred') >>> p.save(using='first') # (statement 1) >>> p.save(using='second') # (statement 2)
В операторе 1 новый объект Person сохраняется в базе данных first. В этот момент у p нет первичного ключа, поэтому Django выполняет оператор SQL INSERT. Это создает первичный ключ, и Django назначает этот первичный ключ p.
Когда сохранение происходит в операторе 2, у p уже есть значение первичного ключа, и Django попытается использовать этот первичный ключ в новой базе данных. Если значение первичного ключа не используется в базе данных second, то у вас не возникнет проблем — объект будет скопирован в новую базу данных.
Однако, если первичный ключ p уже используется в базе данных second, существующий объект в базе данных second будет перезаписан при сохранении p.
Вы можете избежать этого двумя способами. Во-первых, вы можете очистить первичный ключ экземпляра. Если у объекта нет первичного ключа, Django будет обрабатывать его как новый объект, предотвращая потерю данных в базе данных second:
>>> p = Person(name='Fred') >>> p.save(using='first') >>> p.pk = None # Clear the primary key. >>> p.save(using='second') # Write a completely new object.
Второй вариант — использовать опцию force_insert к save() для обеспечения выполнения Django оператора SQL INSERT:
>>> p = Person(name='Fred') >>> p.save(using='first') >>> p.save(using='second', force_insert=True)
Это гарантирует, что человек по имени Fred будет иметь тот же первичный ключ в обеих базах данных. Если этот первичный ключ уже используется при попытке сохранения в базу данных second, будет выведено сообщение об ошибке.
Выбор базы данных для удаления
По умолчанию вызов удаления существующего объекта будет выполнен в той же базе данных, которая использовалась для извлечения объекта в первую очередь:
>>> u = User.objects.using('legacy_users').get(username='fred')
>>> u.delete() # will delete from the `legacy_users` database
Чтобы указать базу данных, из которой будет удалена модель, передайте ключевой аргумент using в метод Model.delete(). Этот аргумент работает так же, как и ключевой аргумент using для save().
Например, если вы мигрируете пользователя из базы данных legacy_users в базу данных new_users, вы можете использовать эти команды:
>>> user_obj.save(using='new_users') >>> user_obj.delete(using='legacy_users')
Использование менеджеров с несколькими базами данных
Используйте метод db_manager() на менеджерах, чтобы предоставить менеджерам доступ к базе данных, отличной от базы данных по умолчанию.
Например, предположим, что у вас есть пользовательский метод менеджера, который взаимодействует с базой данных — User.objects.create_user(). Поскольку create_user() — это метод менеджера, а не метод QuerySet, вы не можете сделать User.objects.using('new_users').create_user(). (Метод create_user() доступен только для User.objects, менеджера, а не для объектов QuerySet, полученных из менеджера.) Решение — использовать db_manager(), как в этом примере:
User.objects.db_manager('new_users').create_user(...)
db_manager() возвращает копию менеджера, связанного с указанной базой данных.
Использование get_queryset() с несколькими базами данных
Если вы переопределяете get_queryset() в своем менеджере, обязательно вызовите этот метод на родительском объекте (используя super()) или выполните соответствующую обработку атрибута _db менеджера (строка, содержащая имя базы данных для использования).
Например, если вы хотите вернуть пользовательский класс QuerySet из метода get_queryset, вы можете сделать это так:
class MyManager(models.Manager):
def get_queryset(self):
qs = CustomQuerySet(self.model)
if self._db is not None:
qs = qs.using(self._db)
return qs
Отображение нескольких баз данных в интерфейсе администратора Django
В интерфейсе администратора Django нет явной поддержки нескольких баз данных. Если вы хотите предоставить интерфейс администратора для модели в базе данных, отличной от той, которая задана цепочкой вашего маршрутизатора, вам нужно написать пользовательские классы ModelAdmin, которые будут направлять администратора к использованию определенной базы данных для контента.
Объекты ModelAdmin имеют пять методов, требующих настройки для поддержки нескольких баз данных:
class MultiDBModelAdmin(admin.ModelAdmin):
# A handy constant for the name of the alternate database.
using = 'other'
def save_model(self, request, obj, form, change):
# Tell Django to save objects to the 'other' database.
obj.save(using=self.using)
def delete_model(self, request, obj):
# Tell Django to delete objects from the 'other' database
obj.delete(using=self.using)
def get_queryset(self, request):
# Tell Django to look for objects on the 'other' database.
return super(MultiDBModelAdmin, self).get_queryset(request).using(self.using)
def formfield_for_foreignkey(self, db_field, request=None, **kwargs):
# Tell Django to populate ForeignKey widgets using a query
# on the 'other' database.
return super(MultiDBModelAdmin, self).formfield_for_foreignkey(db_field, request=request, using=self.using, **kwargs)
def formfield_for_manytomany(self, db_field, request=None, **kwargs):
# Tell Django to populate ManyToMany widgets using a query
# on the 'other' database.
return super(MultiDBModelAdmin, self).formfield_for_manytomany(db_field, request=request, using=self.using, **kwargs)
Представленная здесь реализация реализует стратегию многобазовой обработки, где все объекты заданного типа хранятся в определенной базе данных (например, все объекты User хранятся в базе данных other). Если ваше использование нескольких баз данных более сложное, ваши определения ModelAdmin должны отражать эту стратегию.
Обработка вложенных элементов может быть выполнена аналогичным образом. Они требуют трех настраиваемых методов:
class MultiDBTabularInline(admin.TabularInline):
using = 'other'
def get_queryset(self, request):
# Tell Django to look for inline objects on the 'other' database.
return super(MultiDBTabularInline, self).get_queryset(request).using(self.using)
def formfield_for_foreignkey(self, db_field, request=None, **kwargs):
# Tell Django to populate ForeignKey widgets using a query
# on the 'other' database.
return super(MultiDBTabularInline, self).formfield_for_foreignkey(db_field, request=request, using=self.using, **kwargs)
def formfield_for_manytomany(self, db_field, request=None, **kwargs):
# Tell Django to populate ManyToMany widgets using a query
# on the 'other' database.
return super(MultiDBTabularInline, self).formfield_for_manytomany(db_field, request=request, using=self.using, **kwargs)
После написания определений модели администратора их можно зарегистрировать с любым экземпляром Admin:
from django.contrib import admin
# Specialize the multi-db admin objects for use with specific models.
class BookInline(MultiDBTabularInline):
model = Book
class PublisherAdmin(MultiDBModelAdmin):
inlines = [BookInline]
admin.site.register(Author, MultiDBModelAdmin)
admin.site.register(Publisher, PublisherAdmin)
othersite = admin.AdminSite('othersite')
othersite.register(Publisher, MultiDBModelAdmin)
В этом примере создаются два сайта администратора. На первом сайте отображаются объекты Author и Publisher; для объектов Publisher есть табличный вложенный элемент, показывающий книги, опубликованные этим издателем. Второй сайт отображает только издателей без вложенных элементов.
Использование необработанных курсоров с несколькими базами данных
Если вы используете более одной базы данных, вы можете использовать django.db.connections для получения подключения (и курсора) для определенной базы данных. django.db.connections — это объект, похожий на словарь, который позволяет получить определенное подключение по его псевдониму:
from django.db import connections cursor = connections['my_db_alias'].cursor()
Ограничения нескольких баз данных
Связи между базами данных
В настоящее время Django не поддерживает связи по внешнему ключу или многие-ко-многим, охватывающие несколько баз данных. Если вы использовали маршрутизатор для разделения моделей на разные базы данных, все внешние ключи и связи многие-ко-многим, определенные этими моделями, должны быть внутренними для одной базы данных.
Это связано с целостностью ссылок. Чтобы сохранить связь между двумя объектами, Django должно знать, что первичный ключ связанного объекта действителен. Если первичный ключ хранится в отдельной базе данных, легко оценить действительность первичного ключа не представляется возможным.
Если вы используете Postgres, Oracle или MySQL с InnoDB, это обеспечивается на уровне целостности базы данных — ограничения ключей на уровне базы данных предотвращают создание связей, которые невозможно проверить.
Однако, если вы используете SQLite или MySQL с таблицами MyISAM, принудительная целостность ссылок не применяется; в результате вы можете «подделать» связи по внешнему ключу между базами данных. Однако эта конфигурация не официально поддерживается Django.
Поведение приложений contrib
Несколько приложений contrib содержат модели, и некоторые приложения зависят от других. Поскольку связи между базами данных невозможны, это создает некоторые ограничения на то, как вы можете разделить эти модели на базы данных:
- каждая из
contenttypes.ContentType,sessions.Sessionиsites.Siteможет храниться в любой базе данных при наличии подходящего маршрутизатора. -
модели
auth—User,GroupиPermission— взаимосвязаны и связаны сContentType, поэтому они должны храниться в той же базе данных, что иContentType. -
модели
adminзависят отauth, поэтому их модели должны храниться в той же базе данных, что иauth. -
модели
flatpagesиredirectsзависят отsites, поэтому их модели должны храниться в той же базе данных, что иsites.
Кроме того, некоторые объекты автоматически создаются сразу после того, как migrate создает таблицу для их хранения в базе данных:
- по умолчанию
Site, - a
ContentTypeдля каждой модели (включая те, которые не хранятся в этой базе данных), - три
Permissionдля каждой модели (включая те, которые не хранятся в этой базе данных).
Для распространённых конфигураций с несколькими базами данных нет необходимости иметь эти объекты в более чем одной базе данных. Распространённые конфигурации включают первичную/реплицированную и подключение к внешним базам данных. Поэтому рекомендуется написать маршрутизатор баз данных маршрутизатор базы данных, который позволит синхронизировать эти три модели только с одной базой данных. Используйте тот же подход для приложений contrib и сторонних приложений, которым не нужно, чтобы их таблицы находились в нескольких базах данных.
Предупреждение
Если вы синхронизируете типы контента с более чем одной базой данных, имейте в виду, что их первичные ключи могут не совпадать между базами данных. Это может привести к повреждению данных или потере данных.
© Django Software Foundation and individual contributors
Licensed under the BSD License.
https://docs.djangoproject.com/en/1.8/topics/db/multi-db/