如何删除SQLAlchemy多对多关联中的数据库孤立数据?
问题根因
- 通用问题1:
relationship配置了lazy='dynamic',动态加载的关系不会在会话中留存属性变更历史,auto_delete_orphans和手动监听的get_history都无法检测到关联变更。 - 通用问题2:
relationship配置了passive_deletes=True,该配置会让SQLAlchemy跳过主动处理关联记录的逻辑,直接依赖数据库外键的级联删除,会话无法感知到关键词和子板块的关联已被移除,不会触发孤儿检测逻辑。 - 方案2专属问题:你手动创建了新的
Session实例并绑定了事件监听器,但实际业务中使用的是Flask-SQLAlchemy提供的db.session,二者完全独立,监听逻辑根本不会被触发。
修复方案
步骤1:调整关系配置
把不需要的参数移除,若需保留动态查询能力可单独封装查询属性,示例代码:
subreddits_keywords = db.Table('subreddits_keywords', db.Model.metadata, db.Column('subreddit_id', db.Integer, db.ForeignKey('subreddits.id', ondelete='CASCADE')), db.Column('keyword_id', db.Integer, db.ForeignKey('keywords.id', ondelete='CASCADE')), ) class Subreddit(db.Model, JsonSerializer): __tablename__ = 'subreddits' id = db.Column(db.Integer, primary_key=True) subreddit_name = db.Column(db.String(128), index=True) # 移除lazy='dynamic'和passive_deletes参数 keywords = db.relationship('Keyword', secondary=subreddits_keywords, backref='subreddits', cascade='all, delete') # 若需保留动态查询能力,单独新增属性 @property def keywords_dynamic(self): return db.session.query(Keyword).with_parent(self, "keywords") class Keyword(db.Model, JsonSerializer): __tablename__ = 'keywords' id = db.Column(db.Integer, primary_key=True) keyword = db.Column(db.String(128), index=True)
步骤2:选择孤儿删除实现
方案A:使用sqlalchemy-utils的auto_delete_orphans
配置完关系后直接调用即可:
from sqlalchemy_utils import auto_delete_orphans auto_delete_orphans(Subreddit.keywords)
方案B:手动实现事件监听
将监听器绑定到实际使用的db.session上:
from sqlalchemy import event from sqlalchemy.orm import attributes @event.listens_for(db.session, 'after_flush') def delete_keyword_orphans(session, ctx): need_delete_orphan = False # 检测有关联变更的子板块实例 for instance in session.dirty: if isinstance(instance, Subreddit) and attributes.get_history(instance, 'keywords').deleted: need_delete_orphan = True break # 检测被删除的子板块实例 for instance in session.deleted: if isinstance(instance, Subreddit): need_delete_orphan = True break # 删除无关联的孤儿关键词 if need_delete_orphan: session.query(Keyword).filter(~Keyword.subreddits.any()).delete(synchronize_session=False)
注意:删除子板块实例后需要执行
db.session.commit()提交事务,孤儿关键词才会被自动清除。
内容的提问来源于stack exchange,提问作者Brendan
相关产品推荐
相关产品推荐

