Flask-SQLAlchemy多数据库配置必须显式声明SQLALCHEMY_BINDS字典并为模型设置__bind_key__,db.session默认只连主库;读写分离需手动构造从库session,不可依赖query_class自动路由,且主从延迟下强一致读必须走主库。

Flask-SQLAlchemy 多数据库配置必须显式声明 binds 字典
不手动定义 SQLALCHEMY_BINDS,所有模型默认只连主库,根本不会路由到从库。这不是“自动发现”机制,而是完全依赖字典键名匹配。
常见错误是只配了多个数据库 URL,却忘了在 app.config 里声明 binds:
# ✅ 正确:显式绑定别名
app.config['SQLALCHEMY_BINDS'] = {
'master': 'mysql+pymysql://user:pass@master/db',
'slave1': 'mysql+pymysql://user:pass@slave1/db',
'slave2': 'mysql+pymysql://user:pass@slave2/db'
}模型类需通过 __bind_key__ 指定归属:
class User(db.Model):
__bind_key__ = 'master' # 写操作必须走这里
id = db.Column(db.Integer, primary_key=True)
<p>class Log(db.Model):
__bind_key__ = 'slave1' # 查询日志可走从库
id = db.Column(db.Integer, primary_key=True)-
__bind_key__值必须与SQLALCHEMY_BINDS字典的 key 完全一致,大小写敏感 - 未设
__bind_key__的模型始终使用主库(SQLALCHEMY_DATABASE_URI) - 一个模型不能动态切换 bind;如需读写分离,得拆成两个模型或用原生查询
读写分离不能靠 db.session 自动判断,得手动控制会话绑定
Flask-SQLAlchemy 的 db.session 默认绑定主库,即使模型设了 __bind_key__ = 'slave1',调用 User.query.all() 仍可能报错 —— 因为查询发起时 session 没关联从库引擎。
立即学习“Python免费学习笔记(深入)”;
正确做法是用 db.get_engine(app, bind='slave1') 构造独立 session:
from sqlalchemy.orm import sessionmaker <p>slave_engine = db.get_engine(app, bind='slave1') SlaveSession = sessionmaker(bind=slave_engine) session = SlaveSession()</p><h1>✅ 安全读取</h1><p>users = session.query(User).filter(User.active == True).all() session.close()
- 不要复用
db.session做从库查询;它生命周期和事务上下文都绑定主库 - 若用
scoped_session,需为每个 bind 单独注册,否则线程内 session 会混用 - 写操作(
add/commit)必须用主库 session,且不能和从库 session 混用同一个事务
自定义查询路由要避开 query_class 的陷阱
有人试图给模型加 query_class 实现“读自动走从库”,但这是危险的:Flask-SQLAlchemy 的 query 属性是描述符,底层仍依赖 session 绑定,强行改 class 容易导致 AttributeError: 'xxx' object has no attribute '_sa_instance_state'。
更稳妥的方式是封装一个路由函数:
import random <p>def get_read_session(): slaves = ['slave1', 'slave2'] bind = random.choice(slaves) # 简单轮询,可替换为权重或健康检查 engine = db.get_engine(current_app, bind=bind) return sessionmaker(bind=engine)()
- 避免在模型层做路由逻辑,容易和 ORM 生命周期冲突
- 连接池设置要区分主从:
pool_pre_ping=True推荐仅对主库启用,从库可适当降低pool_recycle - 如果用了
flask-sqlalchemy>=3.0,注意db.session已变为 scoped,db.create_scoped_session()不再接受bind参数,必须用sessionmaker显式构造
事务与跨库操作无法保证一致性,必须设计降级策略
MySQL 主从复制有延迟,INSERT 后立刻 SELECT 从库大概率查不到新数据。这不是代码 bug,是架构约束。
- 强一致读(比如刚注册就跳转个人页)必须走主库查询,哪怕多一次 DB 调用
- 不要跨 bind 做 JOIN 查询;SQLAlchemy 不支持多引擎 JOIN,会直接抛
InvalidRequestError - 分库字段(如 user_id)若用于路由,需确保写入时已知目标库,否则得用全局唯一 ID 或中间件透传
最常被忽略的一点:从库挂掉时,读请求不能卡死。要在 get_read_session() 里加超时和重试,而不是让整个接口 500。


















