【问题标题】:Read slave, read-write master setup读取从机,读写主机设置
【发布时间】:2012-02-15 09:39:50
【问题描述】:

我有一个使用单个 mysql 服务器的 Flask,SQLAlchemy webapp。我想扩展数据库设置以拥有一个只读的从服务器,这样我就可以在主从服务器之间传播读取,同时继续写入主数据库服务器。

我看过几个选项,我相信我不能用普通的 SQLAlchemy 做到这一点。相反,我打算在我的 web 应用程序中创建 2 个数据库句柄,一个用于主数据库服务器和从属数据库服务器。然后使用简单的随机值使用主/从数据库句柄进行“SELECT”操作。

但是,我不确定这是否是使用 SQLAlchemy 的正确方法。关于如何实现这一点的任何建议/提示?

【问题讨论】:

    标签: python mysql sqlalchemy flask flask-sqlalchemy


    【解决方案1】:

    我的博客http://techspot.zzzeek.org/2012/01/11/django-style-database-routers-in-sqlalchemy/ 上有一个如何执行此操作的示例。基本上,您可以增强 Session 以便它在逐个查询的基础上从主服务器或从服务器中进行选择。这种方法的一个潜在故障是,如果您有一个调用六个查询的事务,您最终可能会在一个请求中使用两个从属设备....但是我们只是在尝试模仿 Django 的功能:)

    我使用的一种稍微不那么神奇的方法,它也更明确地确定了使用范围,是视图可调用对象(无论它们在 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 将断言 Session我们在这里制作的绝对是全新的。

    您将它指向所有后续 SQL 的“从属”。然后,当请求结束时,您将确保您的 Flask 应用程序正在调用 Session.remove() 以清除该线程的注册表。当注册表下次在同一个线程上使用时,它将是一个绑定回“master”的新 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 绑定到一个“从属”,装饰器将其置于“主”以进行写操作。如果在这种情况下你想要一个随机的从站,如果 Flask 有某种“请求开始”事件,你可以在那个时候设置它。

    【讨论】:

    • Thnx zzzeek 这很有帮助。感谢所有在 sqlalchemy 上的出色工作。
    • Rad 评论,还有很棒的代码示例!如果 sqlalchemy 有某种方法可以自动进行查询分析和路由,那就太好了,但是在一个查询可能导致 tmp 表或其他写操作的世界中,因为通常可能是只读的,这需要像请求这样的东西在提交查询之前从后端获取查询计划,并且在大多数情况下会比它值得更麻烦。
    • 我们有“查询分析”选项,尽管它需要您自己编写分析。水平分片系统说明了这种技术的一个示例,请参阅docs.sqlalchemy.org/en/rel_0_7/orm/extensions/…
    • 挂钩有一个回调函数来决定如何对查询进行分片是很好的,但实际上像正确的查询检查分类来分离读取操作和写入操作可能超出了大多数人的范围sqlalchemy 用户。真的,我希望在不久的将来的某个时候,这些策略的一些基本实现可以作为 SA 中的样板。
    • 在某些情况下分析查询以选择会话将是一个坏主意。您希望读取与写入在同一事务中完成,以防您的读取受到写入操作的影响。想象一下,在同一个调用中,您删除了表中的一行,然后返回该表中所有记录的列的总和。如果您使用两个不同的事务,总和将包含您刚刚删除的行,因为另一个事务尚未提交。
    【解决方案2】:

    或者,我们可以尝试另一种方式。例如,我们可以声明两个不同的类,所有实例属性相同,但__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'    
    

    【讨论】:

    • 您能否通过提供创建、定义和使用具有不同读写访问能力的两个数据库的示例来改进您的答案
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2020-07-05
    • 1970-01-01
    • 2011-01-23
    • 2021-10-13
    • 1970-01-01
    相关资源
    最近更新 更多