
sqlite 默认不启用外键约束,即使表结构中已定义 foreign key,若未显式开启 `pragma foreign_keys = on`,删除主表记录时将不会触发约束检查,导致数据不一致。本文详解其原理、验证方法及可靠解决方案。
在使用 SQLAlchemy + SQLite 开发应用时,你可能会遇到一个看似矛盾的现象:数据库表结构明确声明了外键(如 FOREIGN KEY(list_id) REFERENCES mastolist(id)),但在 Python 代码中调用 session.delete() 删除父记录(如 MastoList)后,子表(如 Toot)中仍保留指向已删主键的脏数据,且未抛出任何异常——而同样的删除操作在 DB Browser for SQLite 中却会立即报错 FOREIGN KEY constraint failed。
根本原因在于:SQLite 默认禁用外键约束检查(自 3.6.19 版本起为兼容旧应用,默认 foreign_keys = OFF)。这意味着,即使 DDL 中包含 FOREIGN KEY 子句,SQLite 仅将其视为注释,不会实际执行约束验证,除非显式启用。
✅ 验证当前状态:
在 SQLite 命令行或 DB Browser 中执行:
PRAGMA foreign_keys;
若返回 0,即表示外键未启用。
? 正确启用方式(推荐在应用初始化时执行):
from sqlalchemy import create_engine
# 创建引擎时强制启用外键
engine = create_engine(
"sqlite:///app.db",
connect_args={"check_same_thread": False},
echo=True
)
# 在首次连接后立即执行 PRAGMA(推荐方式)
@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 是连接级设置,每个新连接需单独启用;仅执行一次 PRAGMA 不会影响后续连接。
- SQLAlchemy 的 backref 或 relationship() 配置(如 db.Relationship('Toot', backref='mastolist'))不会自动启用 SQLite 外键,它仅影响 ORM 层级的级联行为(如 cascade="all, delete-orphan"),属于应用逻辑控制,与数据库层面的约束无关。
- 若依赖 ORM 级联删除(如 cascade="delete"),务必配合 passive_deletes=False(默认值)确保 SQLAlchemy 主动加载并删除子对象;但该机制无法替代数据库外键的完整性保障。
? 最佳实践建议:
- 始终启用 PRAGMA foreign_keys = ON —— 这是保障参照完整性的第一道防线;
- 在模型中显式声明级联策略(如 cascade="all, delete-orphan"),实现业务逻辑与数据一致性的双重保障;
- 避免仅依赖 backref 触发外键错误——它只是巧合生效(因 SQLAlchemy 在检测到 relationship 后会主动查询子记录,可能引发约束检查),并非设计意图。
总结:SQLite 的外键是“光学外键”——存在但不工作,除非你亲手打开开关。启用 PRAGMA foreign_keys = ON 是让数据库真正履行约束职责的必要前提,也是构建健壮数据层的基石。










