跳到主要内容

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 一对多关系