软删除不能只靠query.filter(),因手动添加is_deleted==false易遗漏、难维护,且无法统一拦截model.query和关系加载等所有查询入口;必须通过自定义softdeletequery类重写filter方法,在查询构造时自动注入软删除条件。

Flask-SQLAlchemy里软删除为什么不能只靠query.filter()?
因为每次手动加 is_deleted == False 容易漏、难维护,且无法统一拦截原生 Model.query 或关系加载(比如 user.posts)中的查询。硬编码过滤条件会污染业务逻辑,也绕不开 db.session.query(Model) 这类底层调用。
真正要“优雅”,得让所有读取操作默认忽略已软删除记录,就像它们不存在一样——这必须从查询入口层拦截,而不是在每个视图里补条件。
自定义BaseQuery如何接管默认查询行为?
Flask-SQLAlchemy 的 Query 类支持继承并重写 __iter__、all、first 等方法,但最稳妥的方式是重写 filter 和 filter_by,并在构造时自动注入软删除条件。
- 定义一个继承自
Query的子类,如SoftDeleteQuery - 在
filter方法中检查是否已存在is_deleted相关条件;若无,则追加is_deleted == False - 注意:必须用
self._criterion判断原始条件,不能简单看参数——否则会误判filter(None)这类调用 - 给模型指定
query_class = SoftDeleteQuery,或全局配置db.Query = SoftDeleteQuery(后者影响所有模型,慎用)
示例关键片段:
class SoftDeleteQuery(Query):
def filter(self, *criterion):
# 检查是否已有 is_deleted 条件
has_deleted_filter = any(
hasattr(c, 'name') and c.name == 'is_deleted' for c in criterion
)
if not has_deleted_filter:
return super().filter(self.model.is_deleted == False, *criterion)
return super().filter(*criterion)
怎么处理关联查询和 eagerload 中的软删除?
默认情况下,joinedload 或 selectinload 加载的关联对象不会自动应用软删除过滤,会导致查出已删除的关联记录(比如用户已删除的评论仍出现在 user.comments 中)。
- 必须为关联字段显式设置
lazy='dynamic'并配合自定义 query_class,或改用lazy='selectin'+ 在关联模型上也启用软删除 query_class - 更可靠的做法:在关系定义中使用
primaryjoin显式加入软删除条件,例如:comments = relationship("Comment", primaryjoin="and_(Comment.user_id==User.id, Comment.is_deleted==False)") - 注意:
primaryjoin中不能直接用User.is_deleted引用本模型软删除字段(除非你也对 User 启用了软删除),否则会出错
restore() 和 delete() 方法容易踩哪些坑?
很多人把 delete() 改成更新 is_deleted=True,但忘了它不触发 ORM 事件(如 @validates、before_update),也不走 session flush 流程。
- 推荐实现为实例方法:
def delete(self): self.is_deleted = True,然后由业务层显式调用db.session.commit() - 避免重写
__delete__或覆盖 SQLAlchemy 的delete()类方法——它原本是用于批量删除语句的,语义不符 -
restore()要小心并发:如果两个请求同时 restore 同一条记录,可能产生脏写;建议加数据库唯一约束(如联合索引(id, is_deleted))或用UPDATE ... WHERE is_deleted = True返回受影响行数做校验 - 别在
delete()里调用db.session.delete(self)——那会真删,违背软删除本意
软删除真正的复杂点不在“怎么标为删除”,而在于**所有读路径是否一致、可预测地过滤掉它们**——包括 raw SQL、join、subquery、count()、exists(),甚至第三方扩展(如 Flask-Admin)的默认行为。漏掉任一环节,数据一致性就破了。
Python免费学习笔记(深入):立即使用
在学习笔记中,你将探索 Python 的核心概念和高级技巧!











