自動數(shù)據(jù)庫路由

2022-03-16 17:38 更新

使用多數(shù)據(jù)庫最簡單的方式就是設(shè)置數(shù)據(jù)庫路由方案。默認路由方案確保對象對原始數(shù)據(jù)庫保持粘性(比如,從 ?foo ?數(shù)據(jù)庫檢索到的對象將被保持到同一個數(shù)據(jù)庫)。默認路由方案確保當數(shù)據(jù)庫沒有指定時,所有查詢回退到 ?default ?數(shù)據(jù)庫。

你無需執(zhí)行任何操作來激活默認路由——在每個 Django 項目上是開箱即用的。然而,如果想實現(xiàn)更多有趣的數(shù)據(jù)庫分配行為,可以定義和安裝自己的數(shù)據(jù)庫路由。

數(shù)據(jù)庫路由

數(shù)據(jù)庫路由是一個類,它提供四種方法:

db_for_read(model, **hints)

建議用于讀取?model?類型對象的數(shù)據(jù)庫。
如果數(shù)據(jù)庫操作可以提供有助于選擇數(shù)據(jù)庫的任何附加信息,它將在 ?hints ?中提供。
如果沒有建議,則返回 ?None ?。

db_for_write(model, **hints)

建議用于寫入?model?類型對象的數(shù)據(jù)庫。
如果數(shù)據(jù)庫操作可以提供有助于選擇數(shù)據(jù)庫的任何附加信息,它將在 ?hints ?中提供。
如果沒有建議,則返回 ?None ?。

allow_relation(obj1, obj2, **hints)

如果允許 ?obj1 ?和 ?obj2 ?之間的關(guān)系,返回 ?True ?。如果阻止關(guān)系,返回 ?False ?,或如果路由沒意見,則返回 ?None?。這純粹是一種驗證操作,由外鍵和多對多操作決定是否應(yīng)該允許關(guān)系。
如果沒有路由有意見(比如所有路由返回 ?None?),則只允許同一個數(shù)據(jù)庫內(nèi)的關(guān)系。

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

決定是否允許遷移操作在別名為 ?db ?的數(shù)據(jù)庫上運行。如果操作運行,那么返回 ?True ?,如果沒有運行則返回 ?False ?,或路由沒有意見則返回 ?None ?。
?app_label ?參數(shù)是要遷移的應(yīng)用程序的標簽。
?model_name ?由大部分遷移操作設(shè)置來要遷移的模型的 ?model._meta.model_name? (模型 ?__name__? 的小寫版本) 的值。 對于 RunPython 和 RunSQL 操作的值是 ?None ?,除非它們提示要提供它。
?hints通過某些操作來向路由傳達附加信息。
當設(shè)置 ?model_name ?,?hints? 通常包含 ?model?下的模型類。注意它可能是 ?historical model? ,因此沒有任何自定義屬性,方法或管理器。你應(yīng)該只能依賴 ?_meta? 。
這個方法也可以用于確定給定數(shù)據(jù)庫上模型的可用性。
?makemigrations ?會在模型變動時創(chuàng)建遷移,但如果 ?allow_migrate()? 返回 ?False?,任何針對 ?model_name? 的遷移操作會在運行 ?migrate的時候跳過。對于已經(jīng)遷移過的模型,改變 ?allow_migrate()? 的行為,可能會破壞主鍵,格外表或丟失的表。當 ?makemigrations ?核實遷移歷史,它跳過不允許遷移的 app 的數(shù)據(jù)庫。

路由不是必須提供所有這些方法——它也許省略它們中的一個或多個。如果某個方法被省略,Django會在執(zhí)行相關(guān)檢查時候,跳過這個路由。

使用路由

數(shù)據(jù)庫路由 ?DATABASE_ROUTERS ?配置安裝。這個配置定義類名列表,每個類名指定了主路由?django.db.router?應(yīng)使用的路由。
Django 的數(shù)據(jù)庫操作使用主路由來分配數(shù)據(jù)庫使用。每當查詢需要知道正在使用哪個數(shù)據(jù)庫時,它會調(diào)用主路由,提供一個模型和提示(如果可用的話),然后 Django 會依次嘗試每個路由直到找到數(shù)據(jù)庫。如果沒有找到,它試著訪問提示實例的當前 ?instance._state.db? 。如果沒有提供提示實例,或者 ?instance._state.db? 為 ?None ?,主路由將分配默認數(shù)據(jù)庫。

例如我們有一些數(shù)據(jù)庫:一個 ?auth ?應(yīng)用,和其他應(yīng)用使用帶有兩個只讀副本的主/副設(shè)置。以下是指定這些數(shù)據(jù)庫的設(shè)置:

DATABASES = {
    'default': {},
    'auth_db': {
        'NAME': 'auth_db_name',
        'ENGINE': 'django.db.backends.mysql',
        'USER': 'mysql_user',
        'PASSWORD': 'swordfish',
    },
    'primary': {
        'NAME': 'primary_name',
        'ENGINE': 'django.db.backends.mysql',
        'USER': 'mysql_user',
        'PASSWORD': 'spam',
    },
    'replica1': {
        'NAME': 'replica1_name',
        'ENGINE': 'django.db.backends.mysql',
        'USER': 'mysql_user',
        'PASSWORD': 'eggs',
    },
    'replica2': {
        'NAME': 'replica2_name',
        'ENGINE': 'django.db.backends.mysql',
        'USER': 'mysql_user',
        'PASSWORD': 'bacon',
    },
}

現(xiàn)在需要處理路由。首先需要一個將 ?auth ?和 ?contenttypes app? 的查詢發(fā)送到 ?auth_db ?的路由(?auth ?模型已經(jīng)關(guān)聯(lián)了 ?ContentType?,因此它們必須保存在同一個數(shù)據(jù)庫里):

class AuthRouter:
    """
    A router to control all database operations on models in the
    auth and contenttypes applications.
    """
    route_app_labels = {'auth', 'contenttypes'}

    def db_for_read(self, model, **hints):
        """
        Attempts to read auth and contenttypes models go to auth_db.
        """
        if model._meta.app_label in self.route_app_labels:
            return 'auth_db'
        return None

    def db_for_write(self, model, **hints):
        """
        Attempts to write auth and contenttypes models go to auth_db.
        """
        if model._meta.app_label in self.route_app_labels:
            return 'auth_db'
        return None

    def allow_relation(self, obj1, obj2, **hints):
        """
        Allow relations if a model in the auth or contenttypes apps is
        involved.
        """
        if (
            obj1._meta.app_label in self.route_app_labels or
            obj2._meta.app_label in self.route_app_labels
        ):
           return True
        return None

    def allow_migrate(self, db, app_label, model_name=None, **hints):
        """
        Make sure the auth and contenttypes apps only appear in the
        'auth_db' database.
        """
        if app_label in self.route_app_labels:
            return db == 'auth_db'
        return None

我們也需要一個發(fā)送所有其他應(yīng)用到主/副配置的路由,并且隨機選擇一個副本來讀?。?/p>

import random

class PrimaryReplicaRouter:
    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_set = {'primary', 'replica1', 'replica2'}
        if obj1._state.db in db_set and obj2._state.db in db_set:
            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

最后,在配置文件中,我們添加下面的代碼(用定義路由器的模塊的實際 Python 路徑替換 ?path.to.? ):

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

處理路由的順序非常重要。路由將按照 ?DATABASE_ROUTERS ?里設(shè)置的順序查詢。在這個例子里, ?AuthRouter ?將在 ?PrimaryReplicaRouter ?前處理,因此,在做出其他決定之前,先處理與 ?auth ?相關(guān)的模型。如果 ?DATABASE_ROUTERS ?設(shè)置在其他順序里列出兩個路由,?PrimaryReplicaRouter.allow_migrate()? 將首先處理。?PrimaryReplicaRouter? 實現(xiàn)的特性意味著所有模型可用于所有數(shù)據(jù)庫。
安裝好這個設(shè)置,并按照 同步數(shù)據(jù)庫 的要求遷移所有的數(shù)據(jù)庫,讓我們運行一些 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')

這個例子定義了一個路由來處理與來自 ?auth ?應(yīng)用的模型交互,其他路由處理與所以其他應(yīng)用的交互。如果 ?default ?為空,并且不想定義一個全能數(shù)據(jù)庫來處理所有未指定的應(yīng)用,那么路由必須在遷移之前處理 ?INSTALLED_APPS ?的所有應(yīng)用名。


以上內(nèi)容是否對您有幫助:
在線筆記
App下載
App下載

掃描二維碼

下載編程獅App

公眾號
微信公眾號

編程獅公眾號