目录
手把手教你用 Peewee 优雅地「改」数据
1. 环境准备
pip install peewee
User 模型:
from peewee import *
db = SqliteDatabase('app.db')
class BaseModel(Model):
class Meta:
database = db
class User(BaseModel):
id = AutoField()
name = CharField()
age = IntegerField()
vip = BooleanField(default=False)
2. 单条记录修改的 3 种姿势
2.1 先查后改(最常用)
# 把 id=1 的用户改名为「张三」
user = User.get_by_id(1)
user.name = '张三'
user.save() # UPDATE user SET name='张三' WHERE id=1;
注意:
- 只有被修改的字段会生成
UPDATE语句,Peewee 内部通过_dirty集合追踪变化。- 如果模型使用了
auto_now=True,save()会自动更新时间戳字段。
2.2 链式 update —— 一条 SQL 搞定
n = (User
.update(name='张三')
.where(User.id == 1)
.execute()) # 返回受影响的行数
- 优点:省去一次
SELECT,适合不需要触发 python 层@property或pre_save钩子的场景。 - 缺点:不会刷新内存对象,不要混用「先查后改」和「链式 update」。
2.3 save() vs update() 对比
| 场景 | save() | update() |
|---|---|---|
需要触发 pre_save/post_save 钩子 | ✅ | ❌ |
| 需要即时刷新内存对象 | ✅ | ❌ |
| 批量更新 10w+ 行 | ❌(N 条 SQL) | ✅(1 条 SQL) |
3. 批量修改的正确姿势
3.1 批量 update —— 一行 SQL 解决
(User
.update(vip=True)
.where(User.age >= 18)
.execute())
3.2 批量 save —— 事务 + 分页
当 update 无法表达业务逻辑(如 python 层加密、日志)时,可用「分页加载 + 批量事务」:
from peewee import chunked
with db.atomic():
# 每次 100 条,防止一次性加载爆内存
for user_batch in chunked(User.select().where(User.age < 18), 100):
for user in user_batch:
user.vip = False
user.save()
小技巧:
chunked()是 Peewee 自带的工具函数,等价于itertools.islice。
4. 原子更新:自增、自减、拼接字段
Peewee 提供了 fn 工具,可以直接在 SQL 层面做原子操作:
from peewee import fn
# age 自增 1
User.update(age=User.age + 1).where(User.id == 1).execute()
# name 末尾拼接「_vip」
User.update(name=fn.CONCAT(User.name, '_vip')).where(User.vip == True).execute()
原子更新可避免并发场景下的「丢失更新」问题。
5. 事务与保存点
with db.atomic() as txn:
User.update(vip=True).execute()
# 业务逻辑...
if some_error:
txn.rollback() # 回滚整个事务
else:
txn.commit()
小技巧:
使用db.savepoint()可以创建嵌套保存点,实现更细粒度回滚。
6. 常见踩坑 & 调试技巧
| 坑 | 原因 | 解决方案 |
|---|---|---|
save() 不生效 | 未调用 save() 或字段未标记为 dirty | 修改属性后确保 save() |
| 链式 update 后内存对象未刷新 | update 不刷新 python 对象 | 重新 get() 或手动赋值 |
| 批量更新超时 | SQLite 默认超时 5s | db = SqliteDatabase('app.db', timeout=20) |
| 更新后外键对象失效 | 外键缓存 | user.refresh()(Peewee 3.15+ 支持) |
调试 SQL:
import logging
logger = logging.getLogger('peewee')
logger.addHandler(logging.StreamHandler())
logger.setLevel(logging.DEBUG)
7. 小结
| 操作 | 推荐写法 |
|---|---|
| 单条更新 | get() → 改字段 → save() |
| 批量更新 | update().where().execute() |
| 原子操作 | update(col=Model.col + 1) |
| 批量 save | atomic() + chunked() |
850

被折叠的 条评论
为什么被折叠?



