Flask-SQLAlchemy单向定义一对多关系的差异与性能疑问
Flask-SQLAlchemy 一对多关系单侧定义的影响与写法对比
嘿,针对你关于Flask-SQLAlchemy一对多关系的问题,我来一步步给你拆解清楚:
一、仅从一侧定义一对多关系的影响
首先得明确:在Flask-SQLAlchemy里,一对多关系的核心是数据库层面的外键约束——这是必须存在的,否则两个表在数据库里没有关联关系。而relationship是ORM提供的便捷工具,属于应用层的封装。
所谓“仅从一侧定义”,通常指的是只在父模型或子模型中的一方配置relationship,但外键已正确定义,这种做法的影响主要体现在ORM层面:
- 如果只在父模型配置
relationship(比如你说的写法一):子模型会通过backref自动获得反向访问属性,让你能双向便捷关联;但如果反过来只在子模型配置relationship(比较少见),父模型就无法直接访问关联的子集合。 - 如果只在子模型定义外键但完全不配置
relationship(写法二):数据库层面的引用完整性依然生效,但ORM层面没有任何便捷的关联访问方式——所有跨表查询都得手动写SQL语句,开发效率会低很多。 - 数据库层面的约束不受影响:只要外键存在,数据库就会保证子表的外键值必须对应父表的主键,不管有没有
relationship。
二、两种写法的区别与性能对比
咱们先把两种写法摆出来,再逐一对比:
写法一:父模型配置relationship+backref
class CustomJob(db.Model): __tablename__ = "custom_job" id = db.Column(db.Integer, primary_key=True, autoincrement=True) country_from = db.Column(db.Integer, db.ForeignKey('country.id')) class Country(db.Model): __tablename__ = "country" id = db.Column(db.Integer, primary_key=True, autoincrement=True) custom_jobs = db.relationship('CustomJob', backref="country", lazy=False)
写法二:仅子模型定义外键
class CustomJob(db.Model): __tablename__ = "custom_job" id = db.Column(db.Integer, primary_key=True, autoincrement=True) country_from = db.Column(db.Integer, db.ForeignKey('country.id')) class Country(db.Model): __tablename__ = "country" id = db.Column(db.Integer, primary_key=True, autoincrement=True)
功能区别
关联访问的便捷性
- 写法一:
- 从
Country实例可以直接通过country.custom_jobs拿到所有关联的CustomJob对象,不用手动写查询。 - 从
CustomJob实例可以通过custom_job.country直接获取对应的Country对象——这是backref="country"自动生成的反向属性,省了很多事儿。
- 从
- 写法二:
- 想从
Country拿关联的CustomJob?得自己写查询:CustomJob.query.filter_by(country_from=country.id).all()。 - 想从
CustomJob拿对应的Country?也得手动查:Country.query.get(custom_job.country_from)。
- 想从
- 写法一:
ORM级别的操作支持
- 写法一:支持很多ORM自带的关联操作,比如给
relationship加cascade="all, delete-orphan"参数,删除Country时会自动删掉关联的CustomJob;或者直接给country.custom_jobs添加新的CustomJob实例,提交后ORM会自动设置外键并保存到数据库。 - 写法二:没有这些便捷操作,所有关联数据的修改都得手动处理外键字段,比如新增
CustomJob时要自己设置country_from的值。
- 写法一:支持很多ORM自带的关联操作,比如给
性能差异
写法一中
lazy=False的影响
写法一里的lazy=False表示立即加载:当你查询Country实例时,SQLAlchemy会自动执行JOIN查询,把关联的CustomJob数据一起加载出来。这种方式适合关联数据量不大的场景,能避免后续的N+1查询问题(也就是先查所有Country,再循环查每个Country的CustomJob,导致多次数据库请求)。但如果关联的CustomJob数据很多,单次查询会返回大量数据,反而会拖慢性能。- 如果你把
lazy改成默认的select(延迟加载),则会在第一次访问country.custom_jobs时才执行额外的查询,这时候如果循环多个Country实例,就会出现N+1问题,性能会下降。
- 如果你把
写法二的性能表现
写法二没有relationship,所有跨表查询都得手动写,性能完全取决于你写的SQL语句:- 如果你手动写JOIN查询,性能和写法一中的
lazy=False差不多; - 如果每次都单独查询关联数据,就会和
lazy=select一样出现N+1问题,但好处是你可以手动优化(比如用in_查询批量获取多个Country的CustomJob)。
- 如果你手动写JOIN查询,性能和写法一中的
总结一下:写法一用ORM封装简化了开发,性能取决于lazy参数的配置;写法二需要手动处理所有关联逻辑,灵活性更高但开发效率低,性能完全由你编写的查询语句决定。
内容的提问来源于stack exchange,提问作者Achref Othmeni
相关产品推荐
相关产品推荐

