2012-01-20 150 views
19

我有一個Flask,SQLAlchemy webapp使用單個mysql服務器。我想將數據庫設置擴展爲只讀從服務器,以便在繼續寫入主數據庫服務器的同時在主服務器和從服務器之間傳播讀取內容。讀取從設備,讀寫主設置

我看過很少的選項,我相信我不能用普通的SQLAlchemy做到這一點。相反,我計劃在我的web應用程序中創建2個數據庫句柄,每個數據庫句柄用於主服務器和從服務器數據庫服務器。然後使用一個簡單的隨機值使用主/從數據庫句柄進行「SELECT」操作。

但我不知道這是否正確的方式去使用SQLAlchemy。任何建議/提示如何取消?提前致謝。

回答

26

我有一個如何在我的博客http://techspot.zzzeek.org/2012/01/11/django-style-database-routers-in-sqlalchemy/上執行此操作的示例。基本上,您可以增強會話,以便在逐個查詢的基礎上從主服務器或從服務器中進行選擇。這種方法的一個潛在的小故障是,如果你有一個事務需要6個查詢,你最終可能會在一個請求中使用兩個從屬服務器......但我們只是試圖模仿Django的功能:)

A稍顯不足魔術的辦法,也規定的使用更加明確我用過的範圍上觀點可調用一個裝飾(無論他們是所謂的燒瓶),像這樣:

@with_slave 
def my_view(...): 
    # ... 

with_slave會做這樣的事情,假設你有一個Session和一些引擎設置:

master = create_engine("some DB") 
slave = create_engine("some other DB") 
Session = scoped_session(sessionmaker(bind=master)) 

def with_slave(fn): 
    def go(*arg, **kw): 
     s = Session(bind=slave) 
     return fn(*arg, **kw) 
    return go 

這個想法是調用Session(bind=slave)調用註冊表來獲取當前線程的實際Session對象,如果它不存在則創建它 - 但是由於我們傳遞了一個參數,scoped_session將斷言我們的Session這裏絕對是全新的。

您將它指向所有後續SQL的「從屬」。然後,當請求結束時,您可以確保Flask應用程序調用Session.remove()來清除該線程的註冊表。當註冊表接下來在同一個線程中使用時,它將成爲綁定回「主」的新會話。

或變體,你要使用的「奴隸」只爲這一號召,這是因爲它恢復任何現有的綁定回會話「更安全」:

def with_slave(fn): 
    def go(*arg, **kw): 
     s = Session() 
     oldbind = s.bind 
     s.bind = slave 
     try: 
      return fn(*arg, **kw) 
     finally: 
      s.bind = oldbind 
    return go 

對於這些裝飾的你可以將事情顛倒過來,讓會話綁定到一個「奴隸」,在那裏裝飾者將它放在「主」上進行寫操作。如果在這種情況下你想要一個隨機的奴隸,如果Flask有某種「請求開始」事件,那麼你可以在那時設置它。

+2

日Thnx zzzeek這有很大幫助。在sqlalchemy上所有令人敬畏的工作的榮譽。 –

+0

Rad評論,偉大的代碼示例呢!如果sqlalchemy有一些方法可以自動執行查詢分析和路由,那將是非常好的,但在一個查詢可能導致tmp表或其他寫入操作的世界中,由於可能通常只會讀取,而這需要諸如請求在提交查詢之前,來自後端的查詢計劃將比在大多數情況下值得的更麻煩。 –

+1

我們有「查詢分析」選項,但它需要您自己編寫分析。水平分片系統說明了這種技術的一個例子,參見http://docs.sqlalchemy.org/en/rel_0_7/orm/extensions/horizo​​ntal_shard.html。 – zzzeek

0

或者,我們可以嘗試另一種方式。比如我們可以聲明兩個不同的類,其中所有的實例屬性都是一樣的,但是__bind__類的屬性是不同的。因此我們可以使用rw類做讀/寫和r類做只讀。 :)

我覺得這種方式更簡單可靠。 :)

我們聲明瞭兩個數據庫模型,因爲我們可以在兩個不同的數據庫中具有相同名稱的表。這樣,當兩個型號相同的型號__tablename__時,我們也可以繞過'extend_existing'錯誤。

下面是一個例子:

app = Flask(__name__) 
app.config['SQLALCHEMY_BINDS'] = {'rw': 'rw', 'r': 'r'} 
db = SQLAlchemy(app) 
db.Model_RW = db.make_declarative_base() 

class A(db.Model): 
    __tablename__ = 'common' 
    __bind_key__ = 'r' 

class A(db.Model_RW): 
    __tablename__ = 'common' 
    __bind_key__ = 'rw'  
+0

您可以通過提供創建,定義和使用具有不同讀寫訪問能力的兩個數據庫的示例來改進答案 –