django——使用多個數據庫


轉自django官方文檔——使用多個數據庫:http://doc.codingdict.com/django/topics/db/multi-db.html?spm=a2c4e.10696291.0.0.324f19a4oYhfzQ

多數據庫

這篇主題描述Django 對多個數據庫的支持。大部分Django 文檔假設你只和一個數據庫打交道。如果你想與多個數據庫打交道,你將需要一些額外的步驟。

定義你的數據庫

在Django中使用多個數據庫的第一步是告訴Django 你將要使用的數據庫服務器。這通過使用DATABASES 設置完成。該設置映射數據庫別名到一個數據庫連接設置的字典,這是整個Django 中引用一個數據庫的方式。字典中的設置在 DATABASES 文檔中有完整描述。

你可以為數據庫選擇任何別名。然而,default這個別名具有特殊的含義。當沒有選擇其它數據庫時,Django 使用具有default 別名的數據庫。

下面是settings.py的一個示例片段,它定義兩個數據庫 —— 一個默認的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' } } 

如果default 數據庫在你的項目中不合適,你總是需要小心地指定想使用的數據庫。Django 要求default 數據庫必須定義,但是如果不會用到,其參數字典可以保留為空。若要這樣做,你必須為你的所有的應用的模型建立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項目上’直接‘提供。然而,如果你想實現更有趣的數據庫分配行為,你可以定義並安裝你自己的數據庫路由。

數據庫路由

數據庫路由是一個類,它提供4個方法:

db_for_read(model, **hints)

建議model類型的對象的讀操作應該使用的數據庫。

如果一個數據庫操作能夠提供其它額外的信息可以幫助選擇一個數據庫,它將在hints字典中提供。合法的hints 的詳細信息在下文給出。

如果沒有建議,則返回None

db_for_write(model, **hints)

建議Model 類型的對象的寫操作應該使用的數據庫。

如果一個數據庫操作能夠提供其它額外的信息可以幫助選擇一個數據庫,它將在hints字典中提供。 合法的hints 的詳細信息在下文給出。

如果沒有建議,則返回None

allow_relation(obj1, obj2, **hints)

如果obj1 和obj2 之間應該允許關聯則返回True,如果應該防止關聯則返回False,如果路由無法判斷則返回None這是純粹的驗證操作,外鍵和多對多操作使用它來決定兩個對象之間是否應該允許一個關聯。

allow_migrate(db, app_label, model_name=None, **hints)

定義遷移操作是否允許在別名為db的數據庫上運行。如果操作應該運行則返回True ,如果不應該運行則返回False,如果路由無法判斷則返回None

位置參數app_label 是正在遷移的應用的標簽。

大部分遷移操作設置model_name的值為正在遷移的模型的model._meta.model_name(模型的__name__ 的小寫)。對於RunPythonRunSQL 操作它的值為None,除非這兩個操作使用hint 提供它。

hints 用於某些操作來傳遞額外的信息給路由。

當設置了model_name時,hints 通常通過鍵'model'包含該模型的類。注意,它可能是一個歷史模型,因此不會有自定的屬性、方法或管理器。你應該只依賴_meta

這個方法還可以用來決定一個給定數據庫上某個模型的可用性。

注意,如果這個方法返回False,遷移將默默地不會在模型上做任何操作。這可能導致你應用某些操作之后出現損壞的外鍵、表多余或者缺失。

Changed in Django 1.8:

allow_migrate的簽名與以前的版本相比發生了顯着更改。有關詳細信息,請參閱deprecation notes

路由不必提供所有這些方法 —— 它可以省略一個或多個。如果某個方法缺失,在做相應的檢查時Django 將忽略該路由。

提示

Hint 由數據庫路由接收,用於決定哪個數據庫應該接收一個給定的請求。

目前,唯一一個提供的hint 是instance,它是一個對象實例,與正在進行的讀或者寫操作關聯。這可能是正在保存的實例,或者它可能是以多對多關系添加的實例。在某些情況下,不會提供任何實例提示。路由器檢查實例提示的存在,並確定是否應該使用該提示來更改路由行為。

使用路由

數據庫路由使用DATABASE_ROUTERS 設置安裝。這個設置定義一個類名的列表,其中每個類表示一個路由,它們將被主路由(django.db.router)使用。

Django 的數據庫操作使用主路由來分配數據庫的使用。每當一個查詢需要知道使用哪一個數據庫時,它將調用主路由,並提供一個模型和一個Hint (可選)。隨后 Django 依次測試每個路由直至找到一個數據庫的建議。如果找不到建議,它將嘗試Hint 實例的當前_state.db如果沒有提供Hint 實例,或者該實例當前沒有數據庫狀態,主路由將分配default 數據庫。

一個例子

只是為了示例!

這個例子的目的是演示如何使用路由這個基本結構來改變數據庫的使用。它有意忽略一些復雜的問題,目的是為了演示如何使用路由。

如果myapp 中的任何一個模型包含與其它 數據庫之外的模型的關聯,這個例子將不能工作。跨數據的關聯引入引用完整性問題,Django目前還無法處理。

Primary/replica(在某些數據庫中叫做master/slave)配置也是有缺陷的 —— 它不提供任何處理Replication lag 的解決辦法(例如,因為寫入同步到replica 需要一定的時間,這會引入查詢的不一致)。它也不考慮事務與數據庫利用率策略的交互。

那么 —— 在實際應用中這意味着什么?讓我們看一下另外一個配置的例子。這個配置將有幾個數據庫:一個用於auth 應用,所有其它應用使用一個具有兩個讀replica 的 primary/replica。下面是表示這些數據庫的設置:

DATABASES = { '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=None, **hints): """  Make sure the auth app only appears in the 'auth_db'  database.  """ if app_label == 'auth': return db == 'auth_db' return None 

我們還需要一個路由,它發送所有其它應用的查詢到primary/replica 配置,並隨機選擇一個replica 來讀取:

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=None, **hints): """  All non-auth models end up in this pool.  """ return True 

最后,在設置文件中,我們添加如下內容(替換path.to.為該路由定義所在的真正路徑):

DATABASE_ROUTERS = ['path.to.AuthRouter', 'path.to.PrimaryReplicaRouter'] 

路由處理的順序非常重要。路由的查詢將按照DATABASE_ROUTERS設置中列出的順序進行。在這個例子中,AuthRouterPrimaryReplicaRouter之前處理,因此auth中的模型的查詢處理在其它模型之前。如果DATABASE_ROUTERS設置按其它順序列出這兩個路由,PrimaryReplicaRouter.allow_migrate() 將先處理。PrimaryReplicaRouter 中實現的捕獲所有的查詢,這意味着所有的模型可以位於所有的數據庫中。

建立這個配置后,讓我們運行一些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選擇數據庫 。只需要在QuerySet上調用using()就可以讓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() 選擇一個數據庫

Model.save()使用using 關鍵字來指定數據應該保存在哪個數據庫。

例如,若要保存一個對象到legacy_users 數據庫,你應該使用:

>>> my_object.save(using='legacy_users') 

如果你不指定usingsave()方法將保存到路由分配的默認數據庫中。

將對象從一個數據庫移動到另一個數據庫

如果你已經保存一個實例到一個數據庫中,你可能很想使用save(using=...) 來遷移該實例到一個新的數據庫中。然而,如果你不使用正確的步驟,這可能導致意外的結果。

考慮下面的例子:

>>> p = Person(name='Fred') >>> p.save(using='first') # (statement 1) >>> p.save(using='second') # (statement 2) 

在statement 1中,一個新的Person 對象被保存到 first 數據庫中。此時p 沒有主鍵,所以Django 發出一個SQL INSERT 語句。這會創建一個主鍵,且Django 將此主鍵賦值給p

當保存在statement 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 使用一個INSERT SQL:

>>> 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 

要指定刪除一個模型時使用的數據庫,可以對Model.delete()方法使用using 關鍵字參數。這個參數的工作方式與save()using關鍵字參數一樣。

例如,你正在從legacy_users 數據庫到new_users 數據庫遷移一個User ,你可以使用這些命令:

>>> 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屬性(一個包含將要使用的數據庫名稱的字符串)。

例如,如果你想從get_queryset 方法返回一個自定義的 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 對象具有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 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將需要反映相應的策略。

Inlines 可以用相似的方式處理。它們需要3個自定義的方法:

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 對象具有一個表格的內聯,顯示該出版社出版的書籍。第二個站點只暴露Publishers,而沒有內聯。

多數據庫上使用原始游標

如果你正在使用多個數據庫,你可以使用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.ContentTypesessions.Sessionsites.Site 可以存儲在分開存儲在不同的數據庫中,只要給出合適的路由
  • auth模型 —— UserGroupPermission —— 關聯在一起並與ContentType關聯,所以它們必須與ContentType存儲在相同的數據庫中。
  • admin依賴auth,所以它們的模型必須與auth在同一個數據庫中。
  • flatpagesredirects依賴sites,所以它們必須與sites在同一個數據庫中。

另外,migrate在數據庫中創建一張表后,一些對象在該表中自動創建:

  • 一個默認的Site
  • 為每個模型創建一個ContentType(包括沒有存儲在同一個數據庫中的模型),
  • 為每個模型創建3個Permission (包括不是存儲在同一個數據庫中的模型)。

對於常見的多數據庫架構,將這些對象放在多個數據庫中沒有什么用處。常見的數據庫架構包括primary/replica 和連接到外部的數據庫。因此,建議寫一個數據庫路由,它只允許同步這3個模型到一個數據中。對於不需要將表放在多個數據庫中的Contrib 應用和第三方應用,可以使用同樣的方法。

警告

如果你將Content Types 同步到多個數據庫中,注意它們的主鍵在數據庫之間可能不一致。這可能導致數據損壞或數據丟失。


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM