
sqlite数据库默认不启用外键约束,即使表结构中定义了 foreign key,删除被引用的父记录也不会报错;需显式执行 pragma foreign_keys = on 才能激活完整性检查。
在使用 SQLAlchemy 与 SQLite 开发时,你可能会遇到一个看似矛盾的现象:数据库表已正确定义外键(如 Toot.list_id 引用 MastoList.id),但在 Python 代码中调用 session.delete() 删除父记录后,程序成功提交,且子表数据未被级联处理或报错——而直接通过 DB Browser for SQLite 执行相同删除操作却立即触发 FOREIGN KEY constraint failed 错误。
根本原因在于:SQLite 默认禁用外键支持。尽管 CREATE TABLE 语句中包含 FOREIGN KEY(...) 定义,SQLite 仅将其视为注释性声明,不会主动验证约束,除非显式启用。
✅ 正确做法是在应用初始化阶段(如创建引擎时)启用外键:
from sqlalchemy import create_engine
# 启用外键并确保连接时生效
engine = create_engine(
"sqlite:///app.db",
echo=True,
connect_args={"check_same_thread": False}
)
# 在每个新连接上启用外键(推荐方式)
with engine.connect() as conn:
conn.execute(text("PRAGMA foreign_keys = ON"))
conn.commit()更稳健的方式是使用 connect_args 配合事件监听器,确保所有连接自动启用:
from sqlalchemy import event
from sqlalchemy.engine import Engine
@event.listens_for(Engine, "connect")
def set_sqlite_pragma(dbapi_connection, connection_record):
cursor = dbapi_connection.cursor()
cursor.execute("PRAGMA foreign_keys = ON")
cursor.close()⚠️ 注意事项:
- PRAGMA foreign_keys = ON 是连接级别设置,不是数据库文件全局设置,因此必须对每个新连接执行;
- SQLAlchemy 的 backref 或 relationship(如 db.Relationship('Toot', backref='mastolist'))仅影响 ORM 层级行为(如级联删除、加载关联对象),不替代数据库层的外键约束;它可能触发 Python 端的级联逻辑(如 cascade="all, delete"),但若未启用 SQLite 外键,底层仍允许绕过约束的原始 SQL 操作;
- 单纯依赖 ORM 关系而不启用数据库外键,会带来数据一致性风险(例如:直连数据库执行 SQL、多应用共享数据库、或 ORM Bug 绕过)。
? 验证是否生效:
可在任意连接中执行以下查询确认:
with engine.connect() as conn:
result = conn.execute(text("PRAGMA foreign_keys")).scalar()
print("Foreign keys enabled:", result) # 应输出 1✅ 总结:
要真正保障参照完整性,必须 双管齐下:
- 数据库层:启用 PRAGMA foreign_keys = ON(SQLite 必选项);
- ORM 层:合理配置 relationship() 与 cascade 参数(如 cascade="all, delete-orphan")以实现符合业务逻辑的级联行为。
二者缺一不可——前者兜底防脏数据,后者提升开发体验与语义清晰度。










