revision-up-to: | 17812 (1.4) |
---|
リリースノートを参照してください
このトピックガイドでは、 Django がマルチデータベースをサポートしたことについて 述べられています。他の多くの Django ドキュメントでは単一のデータベースを扱う ことを想定しています。マルチデータベースを扱いたいなら、いくつか追加の手順を行う 必要があります。
Django で複数のデータベースを扱うための最初のステップは、使用したいデータベース
サーバについて Django に伝えることです。これには DATABASES
設定を使い
ます。データベースへのエイリアスを設定し、 Django 全体にわたって特定のデータ
ベースを参照できるようにします。特定のデータベースとの接続を設定する辞書です。
内部の辞書の設定については、すべて DATABASES
ドキュメントで述べられて
います。
データベースにはどんなエイリアスでも設定できます。しかし、 default
は特別な
意味を持っています。Django は他のデータベースが一切選ばれていない場合に、
default
によってデータベースを使います。 default
データベースが無い
場合は、常に使うデータベースの指定に気をつけてください。
以下の例は settings.py
の一部で、 2 つのデータベースを定義しています。デフォ
ルトが PostgreSQL データベースで、 users
は MySQL データベースを呼び出し
ます:
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'
}
}
DATABASES
設定に定義していないデータベースにアクセスすると、
django.db.utils.ConnectionDoesNotExist
例外が送出されます。
syncdb
管理コマンドは 1 度に 1 つのデータベースを操作します。デフォ
ルトでは default
データベースが操作されます。 --database
引数
を与えることで、 syncdb に他のデータベースを同期するよう指示できます。例における
全データベースの全モデルを同期するには、以下を実行する必要があります:
$ ./manage.py syncdb
$ ./manage.py syncdb --database=users
どのアプリケーションも特定のデータベースに同期させたくないときは、 database router を定義できます。これによって 特定のモデルに利用制限を設けられます。
他にも、粒度の小さい同期の制御をしたいなら、特定アプリケーションの
sqlall
の出力のすべてか一部を、直接データベースプロンプトにパイプし
ます。こんなかんじです:
$ ./manage.py sqlall sales | ./manage.py dbshell
データベースを扱う他の django-admin.py
コマンドは、 syncdb
と
同じように動作します。今までと同じく、データベースを制御するための
--database
を使用します。は一度に 1 つのデータベース上でのみ動作し
ます。
マルチデータベースを扱う最も簡単な方法は、データベースルーティングスキーム
(a database routing scheme) を設定することです。デフォルトのルーティングスキーム
は、オブジェクトが元のデータベースに ‘ベットリ (sticky)’ なままであることを保証
します (例えば foo
データベースから取ってきたオブジェクトが、同じ foo
データベースに保存されるということ) 。デフォルトのルーティングスキームは、データ
ベースが特定されていないと、全クエリを default
データベースに返します。
デフォルトのルーティングスキームを有効にする必要はありません。 ‘設定する必要なく (out of the box)’ 全 Django プロジェクトで提供されています。より面白いデータ ベースの割り当てをしたいなら、個別のデータベースルータを定義、導入しましょう。
データベースルータは、 4 つのメソッドを提供するクラスです。
db_for_read
(model, **hints)¶model
タイプのオブジェクトの読み込み操作に使われるデータベースを提示
します。
データベース操作においてデータベースを選ぶ助けになる追加の情報を提示できる
なら、 hints
辞書に提供します。妥当なヒントの詳細は
こちら に書かれています。
提示するものが無い場合は None を返します。
db_for_write
(model, **hints)¶Model タイプのオブジェクトの書き込みに使われるデータベースを提示します。
データベース操作においてデータベースを選ぶ助けになる追加の情報を提示できる
なら、 hints
辞書に提供します。妥当なヒントの詳細は
こちら に書かれています。
提示するものが無い場合は None を返します。
allow_relation
(obj1, obj2, **hints)¶obj1 、 obj2 間のリレーションが許可されている場合 True 、リレーションが 許可されていない場合 False 、 ルータに設定がない場合 None を返します。単なる 検証の操作です。外部キー、多対多の操作において、リレーションが 2 オブジェク ト間で許可されているかどうか判別するために使用されます。
allow_syncdb
(db, model)¶model
が db
で与えられたデータベースと同期されるべきかどうかを判別し
ます。モデルが同期されるべきとき True 、 そうでないとき False 、 ルータに
設定がない場合 None を返します。このメソッドは与えられたデータベース上に
モデルが存在するかどうかを判別するのに使えます。
ルータはこれら すべて のメソッドを提供する必要はありません。 1 つかそれ以上は 省略されるでしょう。あるメソッドが省略されれば、 Django は該当のチェックを行う とき、そのルータをスキップします。
データベースルータから受け取ったヒントによって、与えられたリクエストをどのデータ ベースで受け取るべきかを決めます。
今のところ、提供される唯一のヒントは instance
です。実行中の読み込み、もしく
は書き込み操作に関連づけられたオブジェクトのインスタンスです。これは保存された
インスタンスか、多対多のリレーションに追加されたインスタンスになります。場合に
よっては、インスタンスヒントは全く提供されません。ルータはインスタンスヒントの
存在をチェックします。ルーティングの振る舞いを変えるためにヒントが使われるべきか
どうか判断します。
データベースルータは DATABASE_ROUTERS
設定によって導入されます。
この設定ではクラス名のリストを定義します。マスタールータ (django.db.router
)
に使われるべきルータを指定します。
マスタールータは Django のデータベース操作に使われます。これはデータベースに処理
を割り当てるために使われます。クエリがどのデータベースを使うべきか知る必要がある
ときは常にマスタールータが呼び出されます。その際モデルと (有効な場合は) ヒントが
ルータに提供されます。データベースが提示されるまで、 Django は順番に各ルータを
試します。提示されない場合、現在のヒントインスタンスの _state.db
を試し
ます。ヒントインスタンスが提供されないか、インスタンスがデータベースステートを
保持していない場合、マスタールータは default
データベースを割り当てます。
例の狙い!
この例はデモンストレーションを意図しています。ルータインフラストラクチャ (the router infrastructure) が、データベースの使い方を変えるために、どの ように使えるかのデモンストレーションです。デモンストレーションに際して、 いくつかの複雑な問題はあえて無視します。
myapp
中のモデルのうちのどれかが other
データベース外のモデルとの関係
を含んでいれば、この例は動きません。
Cross-database relationships では、
Django が今のところ扱えない参照整合性の問題を紹介しています。
記載されているマスター/スレーブ構成にも欠陥があります。応答の遅延を扱う上で、 なんの解法も提供しません (つまり、クエリ不一致によって、スレーブに伝播させる ための書き込みに時間が取られるからです) 。
実際どんな意味があるのでしょう? myapp
が other
データベースに存在し、
その他のモデルは master
、 slave1
、 slave2
間の、マスター/スレーブ
構成に存在して欲しいとしましょう。これを実現するには、 2 つのルータが必要です:
class MyAppRouter(object):
"""myapp アプリケーションのモデルの全データベース操作を制御するルータ"""
def db_for_read(self, model, **hints):
"myapp モデルの全操作を 'other' に向ける"
if model._meta.app_label == 'myapp':
return 'other'
return None
def db_for_write(self, model, **hints):
"myapp モデルの全操作を 'other' に向ける"
if model._meta.app_label == 'myapp':
return 'other'
return None
def allow_relation(self, obj1, obj2, **hints):
"myqpp 中のモデルが含まれている場合、全リレーションを許可"
if obj1._meta.app_label == 'myapp' or obj2._meta.app_label == 'myapp':
return True
return None
def allow_syncdb(self, db, model):
"myapp アプリケーションが 'other' DB にのみあることを確かなものとする"
if db == 'other':
return model._meta.app_label == 'myapp'
elif model._meta.app_label == 'myapp':
return False
return None
class MasterSlaveRouter(object):
"""単純なマスター/スレーブ構成を設けるルータ"""
def db_for_read(self, model, **hints):
"全読み込み操作をランダムなスレーブに向ける"
return random.choice(['slave1','slave2'])
def db_for_write(self, model, **hints):
"全書き込み操作をマスターに向ける"
return 'master'
def allow_relation(self, obj1, obj2, **hints):
"DB プール内で 2 オブジェクト間のリレーションを許可"
db_list = ('master','slave1','slave2')
if obj1._state.db in db_list and obj2._state.db in db_list:
return True
return None
def allow_syncdb(self, db, model):
"全データベース上に全モデルを明示的に配置する"
return True
ここで設定ファイルに、以下を追加します (path.to.
は、ルータを定義した
モジュールへの実際の Python パスで置き換えます):
DATABASE_ROUTERS = ['path.to.MyAppRouter', 'path.to.MasterSlaveRouter']
ルータが処理される順序は重要です。ルータは DATABASE_ROUTERS
設定に
記述されている順序で問い合わせされます。この例では MyAppRouter
が
MasterSlaveRouter
よりも前に処理されます。結果として、 myapp
中のモデル
に関する決定は、他の決定が行われるより前に処理されます。もし
DATABASE_ROUTERS
設定が、違う順序で 2 つのルータを並べている場合、
MasterSlaveRouter.allow_syncdb()
が最初に処理されるでしょう。
MasterSlaveRouter の多目的な性質が示すのは、全モデルが全データベース上で利用可能
であろうということです。
このセットアップが導入できたら、 Django コードをいくつか走らせてみましょう:
>>> # この検索は 'credentials' データベース上で行われるでしょう
>>> fred = User.objects.get(username='fred')
>>> fred.first_name = 'Frederick'
>>> # この保存も 'credentials' に直接行われます
>>> fred.save()
>>> # これらの検索はスレーブデータベースにランダムに割り当てられます
>>> dna = Person.objects.get(name='Douglas Adams')
>>> # 新しいオブジェクトが作られた際はデータベースに対する割り当てがありません
>>> mh = Book(title='Mostly Harmless')
>>> # この割り当てはルータに助言を求め、 mh を author オブジェクトと同様に、
# 同じデータベースに割り当てます
>>> mh.author = dna
>>> # この保存は、マスターデータベース上に 'mh' インスタンス強制します
>>> mh.save()
>>> # しかし再度オブジェクトを取り出すと、再びスレーブに戻ってきます
>>> mh = Book.objects.get(title='Mostly Harmless')
さらに Django は、コードの中でデータベース処理を完全にコントロールできる API を 提供しています。手動でのデータベース割り当ては、ルータによるデータベース割り当て よりも優先されます。
QuerySet
のためのデータベース手動選択¶QuerySet
“チェーン (chain)” のどの点に置いても、 QuerySet
のためにデータ
ベースを選択できます。ただ、 QuerySet
から using()
を呼び出すだけで、
特定のデータベースを使う他の QuerySet
を取得できます。
using()
は引数を 1 つだけ受け取ります。クエリを走らせたいデータベースへ名を
受け取ります。例えば:
>>> # これは 'default' データベースで実行されます
>>> Author.objects.all()
>>> # So will this.
>>> Author.objects.using('default').all()
>>> # これは 'other' データベースで実行されます
>>> Author.objects.using('other').all()
save()
のためのデータベース選択¶Model.save()
で using
キーワードを使って、どのデータベースにデータを
保存するかを特定できます。
例えば、 legacy_users
データベースへオブジェクトを保存としたいとすると、
このようにします:
>>> my_object.save(using='legacy_users')
using
によって特定しない場合は、 save()
はルータに割り当てられたデフォル
トのデータベースに保存します。
あるデータベースにインスタンスをセーブした場合は、新しいデータベースにインスタ
ンスを移行する手段として、 save(using=...)
を使うためことは魅了的かもしれ
ません。
以下の例を考えてください:
>>> p = Person(name='Fred')
>>> p.save(using='first') # (命令 1)
>>> p.save(using='second') # (命令 2)
命令 1 において、新しい Person
オブジェクトは first
データベースに保存
されています。この場合、 p
はプライマリキーを持たないので、 Django は SQL の
INSERT
命令を発行します。これはプライマリキーを生成し、 Django は p
に
プライマリキーを付与します。
命令 2 によって保存が行われるとき、 p
はすでにプライマリキーの値を保持して
おり、 Djagno はそのプライマリキーを新しいデータベースで使用しようとしています。
そのプライマリキーが second
データベースで使われていない場合は、何の問題は
ありません。新しいデータベースにそのオブジェクトがコピーされます。
しかし、 p
のプライマリキーがすでに second
データベースで使われている
場合は、 second
データベースの現行のオブジェクトは p
が保存された際に
上書きされます。
これ避けるには 2 つの方法があります。 1 つめは、インスタンスのプライマリキーを
消してしまうことです。オブジェクトにプライマリキーが無い場合は、 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.
2 つめの選択肢は、 save()
で force_insert
オプションを使うことです。
これは Django に SQL INSERT
を実行させます。
>>> p = Person(name='Fred')
>>> p.save(using='first')
>>> p.save(using='second', force_insert=True)
これは Fred
という名の人物 (person) が、同じプライマリキーを両方のデータ
ベース上で保有していることを保証します。その second
データベースに書き込もう
としたときにプライマリキーがすでに使われている場合、エラーが送出されます。
デフォルトでは、既存のオブジェクトを削除するという要求は、オブジェクトの取得に 使用されたデータベース上で実行されます:
>>> u = User.objects.using('legacy_users').get(username='fred')
>>> u.delete() # will delete from the `legacy_users` database
モデルを削除するデータベースを特定するために、 using
キーワード引数を
Model.delete()
メソッドに渡します。この引数は save()
の using
キーワード引数と似たように機能します。
例えば、 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
オブジェクト上ではありません) 。この
解法は以下のようになります:
User.objects.db_manager('new_users').create_user(...)
db_manager()
は指定したデータベースに紐づけたマネジャのコピーを返します。
get_query_set()
を使う¶マネジャの get_query_set()
をオーバーライドする際には、 (super()
を
使って) 親を呼び出すか、マネージャの _db
のつく属性 (使うデータベース名
を含む文字列) を適切に扱ってください。
例えば get_query_set
メソッドからカスタムされた QuerySet
クラスを返し
たいなら、こうしてください:
class MyManager(models.Manager):
def get_query_set(self):
qs = CustomQuerySet(self.model)
if self._db is not None:
qs = qs.using(self._db)
return qs
Django の admin は、マルチデータベースの明示的なサポートはしていません。ルータ
チェインによって指定された他のデータベス上のモデルのため、 admin インタフェース
を提供したいなら、カスタム ModelAdmin
クラスを
書く必要があります。特定のデータベースを admin インタフェースのコンテンツに
使用するように命令します。
ModelAdmin
オブジェクトは 5 つのメソッドを持っています。マルチデータベースを
サポートするためカスタマイズできます:
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 queryset(self, request):
# Tell Django to look for objects on the 'other' database.
return super(MultiDBModelAdmin, self).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
にそのストラテジーの反映が必要になります。
インラインは同様の方法で処理できます。メソッドを 3 つカスタマイズします:
class MultiDBTabularInline(admin.TabularInline):
using = 'other'
def queryset(self, request):
# Tell Django to look for inline objects on the 'other' database.
return super(MultiDBTabularInline, self).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 定義を書いた後は、それらは任意の 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.Site('othersite')
othersite.register(Publisher, MultiDBModelAdmin)
この例は 2 つの admin サイトをセットアップします。 1 つめのサイトでは、
Author
、 Publisher
オブジェクトが見られます。 Publisher
オブジェクト
は、その出版社が発行した書籍を表示するインライン表を持っています。
2 つ以上のデータベースを使っているなら、 django.db.connections
を、特定の
データベースへの接続(およびカーソル)を取得するために使えます。
django.db.connections
は辞書ライクオブジェクトです。これによってエイリアスを
使用して特定の接続を取得できます:
from django.db import connections
cursor = connections['my_db_alias'].cursor()
Django は今のところ、複数のデータベースに及ぶ外部キーか多対多リレーションを サポートを提供していません。別のデータベースにモデルを分配するルータを使って いたなら、それらのモデルに定義される、すべての外部キーと多対多リレーションは 1 つのデータベースに入れられます。
これは参照整合のためです。2 オブジェクト間のリレーションを維持するために、 Django は正当に参照されたオブジェクトのプライマリキー知る必要があります。簡単に プライマリキーの妥当性を評価することはできません。
InnoDB で Postgres 、 Oracle 、 MySQL を使っているなら、データベースの整合性 レベルで強制されます。データベースレベルのキーの制約は、検証できないリレーション の生成を防ぎます
MyISAM テーブルで SQLite か MySQL を使っているなら、強制される参照整合性はあり ません。結果として、 ‘偽の (fake)’ クロスデータベースの外部キーができるかも しれません。しかしこの設定は Django で公式にサポートされているものではありま せん。
Oct 26, 2017