3.4 改与删:更新和删除数据
CRUD 收官。更新和删除各有两种方式:ORM 对象式(查出来再改/删,日常首选)和批量语句式(一条 SQL 改一片,高效)。
前置:使用 3.1 节 的
models.py。
一、更新方式1:查出来 → 改属性 → commit(首选)
from models import User, SessionLocal
with SessionLocal() as session:
user = session.get(User, 1)
if user is None:
print("用户不存在")
else:
user.age = 26
user.city = "杭州"
session.commit()
生成的 SQL 很聪明——只更新改过的列:
UPDATE users SET age=?, city=? WHERE users.id = ?
这就是 2.3 节讲的脏数据追踪:Session 记住了你改了哪些属性,commit 时自动生成精准的 UPDATE。没有 session.update() 这种方法,改属性本身就是更新。
常见业务写法:更新接口
def update_user(session, user_id: int, **fields):
"""只更新传入的字段"""
user = session.get(User, user_id)
if user is None:
return None
for key, value in fields.items():
setattr(user, key, value)
session.commit()
return user
# 用法
update_user(session, 1, age=27)
update_user(session, 2, name="李四改", city="广州")
二、更新方式2:update() 批量语句
想"给北京所有用户年龄+1",用方式1得把人全查出来逐个改——又慢又费内存。用 update() 语句一步到位:
from sqlalchemy import update
from models import User, SessionLocal
with SessionLocal() as session:
stmt = (
update(User)
.where(User.city == "北京")
.values(age=User.age + 1) # 可以引用列自身做运算
)
result = session.execute(stmt)
session.commit()
print(f"影响了 {result.rowcount} 行")
生成一条 SQL 搞定全部:
UPDATE users SET age=(users.age + 1) WHERE users.city = ?
两种方式对比
| 对象式(改属性) | 语句式(update()) | |
|---|---|---|
| 适用 | 改一条/几条,且手上有对象 | 按条件批量改一片 |
| 性能 | 每条一句 UPDATE | 一句 SQL 改全部 |
| 触发 Python 侧 default/onupdate | ✅ | ❌(onupdate 中 Python 函数不触发,需注意) |
| 已加载对象自动同步 | ✅ | ❌ 默认不同步(见下方注意) |
⚠️ 语句式更新不会同步内存里已加载的对象。 如果同一 session 里之前查出过某个 user,批量 update 后它的属性还是旧值。简单粗暴的解法:批量更新后需要用到对象时
session.expire_all()或重新查询。
三、删除方式1:查出来 → session.delete(首选)
with SessionLocal() as session:
user = session.get(User, 3)
if user:
session.delete(user)
session.commit()
print(f"已删除 {user.name}")
DELETE FROM users WHERE users.id = ?
四、删除方式2:delete() 批量语句
from sqlalchemy import delete
with SessionLocal() as session:
stmt = delete(User).where(User.age < 18)
result = session.execute(stmt)
session.commit()
print(f"删除了 {result.rowcount} 个未成年用户")
☠️ 恐怖故事时间:
delete(User)不带where会清空整张表。执行批量删除前,请把 where 条件读三遍。稳妥习惯:先用同条件的select count看看会命中多少行。
五、真实项目更常用:软删除
实际业务中很少物理删除数据(误删无法恢复、审计需要留痕)。主流做法是软删除——加一个标记字段:
class User(Base):
__tablename__ = "users"
# ...其他字段...
is_deleted: Mapped[bool] = mapped_column(default=False)
# "删除" = 打标记
user.is_deleted = True
session.commit()
# 所有查询都要过滤掉已删数据
stmt = select(User).where(User.is_deleted == False) # noqa: E712
# 更优雅的写法:
stmt = select(User).where(~User.is_deleted)
代价是每个查询都要记得加过滤条件。是否用软删除取决于业务要求,但你要知道有这个模式。
六、"存在则更新,不存在则插入"(upsert 思路)
新手常见需求,通用写法:
from sqlalchemy import select
def create_or_update_user(session, email: str, **fields):
user = session.scalar(select(User).where(User.email == email))
if user is None:
user = User(email=email, **fields)
session.add(user)
else:
for k, v in fields.items():
setattr(user, k, v)
session.commit()
return user
💡 各数据库还有原生 upsert 语句(SQLite/PG 的
ON CONFLICT、MySQL 的ON DUPLICATE KEY),SQLAlchemy 通过方言模块支持,属于进阶内容,需要时再查文档。
七、CRUD 完整回顾示例
# crud_full_demo.py —— 一个文件走完增删改查
from sqlalchemy import select, update, delete
from models import User, SessionLocal
with SessionLocal() as session:
# ============ C 增 ============
tom = User(name="Tom", email="tom@example.com", age=20, city="北京")
session.add(tom)
session.commit()
print(f"[C] 新增用户 id={tom.id}")
# ============ R 查 ============
found = session.scalar(select(User).where(User.email == "tom@example.com"))
print(f"[R] 查到:{found}")
# ============ U 改(对象式) ============
found.age = 21
session.commit()
print(f"[U] 年龄已改为 {found.age}")
# ============ U 改(语句式) ============
session.execute(update(User).where(User.city == "北京").values(age=User.age + 1))
session.commit()
# ============ D 删 ============
session.delete(found)
session.commit()
print(f"[D] 已删除;再查:{session.get(User, tom.id)}") # None
📝 本节小结
- 更新没有 update 方法:查出对象 → 改属性 → commit,Session 自动生成 UPDATE
- 批量改/删用
update()/delete()语句 +session.execute,一条 SQL 搞定 - 批量语句不带 where = 全表遭殃,执行前默念三遍条件
- 真实项目多用软删除(
is_deleted标记)代替物理删除 result.rowcount能拿到影响的行数
CRUD 毕业!接下来进入 ORM 真正的精华 → 4.1 一对多关系