SQLAlchemy 核心笔记:外键、级联删除与 ORM/数据库的双层防线
一、外键的核心作用
核心目的:保证数据的“参照完整性”,防止出现孤儿数据。
- 限制与校验:子表的关联字段必须在父表中存在(例如:不能给不存在的用户创建订单)。
- 级联操作:父表数据变动时,子表自动跟着变(如:删用户,自动删订单)。
- 建立关联:作为 ORM 定义模型关系(一对多/多对一)的基石。
⚠️ SQLite 巨坑提示:SQLite 默认不生效外键!必须在每次连接时手动开启:
1 2 3 4 5 6
| from sqlalchemy import event @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()
|
二、ForeignKey vs relationship(最容易混淆的点)
它们成对出现,但负责的层面完全不同:
|
ForeignKey |
relationship |
| 作用层 |
🗄️数据库层面(SQL) |
🐍Python 对象层面(ORM) |
| 干了什么 |
告诉数据库:这个字段必须指向另一个表的主键 |
告诉 Python:可以通过属性直接访问关联对象 |
| 没有它会怎样 |
数据库不约束,可插入脏数据 |
只能用order.user_id,不能用 order.user |
| 代码体验: |
|
|
1 2 3 4 5 6
| order = session.query(Order).first() user = session.query(User).filter_by(id=order.user_id).first()
order = session.query(Order).first() print(order.user.name)
|
三、级联删除的双层防线(核心难点)
删除父表数据时,子表数据怎么办?ORM 和数据库各有一套机制:
① 数据库层级联(底线)
1
| user_id = Column(Integer, ForeignKey('users.id', ondelete="CASCADE"))
|
- 谁在干活? 数据库引擎(SQLite/MySQL)
- 怎么干? 收到删除指令时,数据库底层直接用一条 SQL 搞定关联数据的删除。
- 特点:极快(1条 SQL)、绕过 ORM 也生效,但 Python 层面的删除钩子函数不会触发。
② ORM 层级联(防线)
1
| orders = relationship("Order", back_populates="user", cascade="all, delete-orphan")
|
- 谁在干活? SQLAlchemy(Python 代码)
- 怎么干? SQLAlchemy 先查出子表数据,在 Python 里逐个删除,最后再删父表。
- 特点:能触发 Python 的钩子函数,但性能差(N+1 条 SQL),绕过 ORM 就失效。
四、灵魂拷问:两层都写,会不会删两遍冲突?
结论:不会冲突!
类比理解:
- ORM 级联 = 你请的管家(跟着你时很靠谱)
- 数据库级联 = 仓库的硬规矩(不管谁来都管用)
当你通过 ORM 执行 session.delete(user) 时,执行顺序如下:
- 管家先动手:ORM 先查出关联订单,发 SQL 逐个删掉订单,最后删掉用户。
- 仓库空转:当
DELETE FROM users 发到数据库时,关联订单已经被 ORM 删光了。数据库的 ON DELETE CASCADE 想干活,但找不到关联数据,只能安全地空转一下。
如果绕过 ORM 直接写 SQL 呢?
管家(ORM)根本不知道你删了数据,这时候仓库的硬规矩(数据库级联)就起作用了,保证数据绝不留孤儿!
五、最佳实践口诀 🎯
1 2 3 4 5 6 7 8 9 10 11
| class User(Base): __tablename__ = 'users' id = Column(Integer, primary_key=True) orders = relationship("Order", back_populates="user", cascade="all, delete-orphan") class Order(Base): __tablename__ = 'orders' id = Column(Integer, primary_key=True) user_id = Column(Integer, ForeignKey('users.id', ondelete="CASCADE")) user = relationship("User", back_populates="orders")
|
ForeignKey 管数据库(地基),relationship 管 Python(盖楼)。
ORM cascade 是防线,DB ondelete 是底线,两层都写最稳妥!