我有一个使用单个mysql服务器的Flask,SQLAlchemy webapp。我想扩展数据库设置以使其具有只读从属服务器,这样我就可以在主服务器和从服务器之间传播读取,同时继续写入主数据库服务器。
我已经看了几个选项,我相信我不能用简单的SQLAlchemy做到这一点。相反,我计划在我的webapp中创建2个数据库句柄,每个句柄用于主数据库服务器和从数据库服务器。然后使用简单的随机值使用主/从db句柄进行“SELECT”操作。
但是我不确定这是否是使用SQLAlchemy的正确方法。关于如何解决此问题的任何建议/提示?提前谢谢。
答案 0 :(得分:30)
我在http://techspot.zzzeek.org/2012/01/11/django-style-database-routers-in-sqlalchemy/的博客上有一个如何执行此操作的示例。基本上,您可以增强会话,以便它在逐个查询的基础上从主服务器或从服务器中进行选择。这种方法的一个潜在故障是,如果你有一个调用六个查询的事务,你可能最终在一个请求中使用两个从属....但是我们只是试图模仿Django的特性:)
一种稍微不那么神奇的方法,它也更明确地确定了我使用的范围是视图callables的装饰器(无论它们在Flask中被调用),如下所示:
@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将断言我们在这里举办的会议绝对是全新的。
您将其指向所有后续SQL的“slave”。然后,当请求结束时,您将确保Flask应用程序正在调用Session.remove()
以清除该线程的注册表。当注册表接下来在同一个线程上使用时,它将是一个绑定回“master”的新Session。
或者一个变体,你想只为那个调用使用“slave”,这是“更安全”,因为它将任何现有的bind恢复回Session:
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
对于每个这些装饰器,你可以反转一些东西,将Session绑定到一个“slave”,装饰器把它放在“master”上进行写操作。如果在这种情况下你想要一个随机的奴隶,如果Flask有某种“请求开始”事件你可以在那时设置它。
答案 1 :(得分:0)
或者,我们可以尝试另一种方式。例如,我们可以声明两个不同的类,所有实例属性相同但__bind__
类属性不同。因此,我们可以使用 rw 类进行读/写,使用 r 类进行只读。 :)
我认为这种方式更简单可靠。 :)
我们声明了两个db模型,因为我们可以在两个不同的db中使用相同名称的表。这样,当两个具有相同 __ 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'