文章目录
- ✅ 通用思路
- 1. 使用 `merge()` 方法(适用于简单场景)
- 2. 使用数据库特定的 UPSERT 功能(推荐用于性能和并发安全)
- 🟢 PostgreSQL: 使用 `on_conflict_do_update`
- 🟡 MySQL: 使用 `ON DUPLICATE KEY UPDATE`
- 🔵 SQLite: 使用 `ON CONFLICT`
- 💡 小技巧:使用 `add()` + `flush()` + 捕获异常(不推荐用于高并发)
- 📝 总结
在 SQLAlchemy
中,实现 UPSERT(Update or Insert)
的方式取决于你使用的数据库类型(如 PostgreSQL
、MySQL
、SQLite
等),因为不同数据库对 UPSERT
的支持语法不同。下面介绍几种常见的方式:
✅ 通用思路
SQLAlchemy
没有直接提供 upsert
方法,但可以通过以下方式实现:
1. 使用 merge()
方法(适用于简单场景)
merge()
方法用于将给定实例的状态复制到当前Session
中的相应实例中。如果在当前Session
本地找不到目标实例,它会尝试根据主键从数据库加载实例,如果还找不到对象,则创建一个新实例。然后,源实例上每个属性的状态将复制到目标实例。然后,该方法返回生成的目标实例。
from sqlalchemy.orm import Session
from your_model_file import YourModelobj = YourModel(id=1, name="John Doe") # id 是主键
with Session() as session: # 使用上下文管理器自动处理 Session 的创建和关闭,如果有异常会自动回滚session.merge(obj) # 插入或更新数据库session.commit() # 刷新挂起的更改并提交当前事务
🔍
merge()
会根据主键或唯一约束判断是否已存在记录:
- 如果存在,则更新;
- 如果不存在,则插入。
⚠️ 注意:merge()
会创建一个新的实例并返回,并不会修改原对象。
2. 使用数据库特定的 UPSERT 功能(推荐用于性能和并发安全)
🟢 PostgreSQL: 使用 on_conflict_do_update
from sqlalchemy.dialects.postgresql import insertstmt = insert(YourModel).values(id=1, name='John Doe')
stmt = stmt.on_conflict_do_update(index_elements=[YourModel.id], # 主键或唯一索引字段set_=dict(name='John Doe') # 要更新的字段
)session.execute(stmt)
session.commit()
🟡 MySQL: 使用 ON DUPLICATE KEY UPDATE
from sqlalchemy.dialects.mysql import insertstmt = insert(YourModel).values(id=1, name='John Doe')
stmt = stmt.on_duplicate_key_update(name='John Doe')session.execute(stmt)
session.commit()
🔵 SQLite: 使用 ON CONFLICT
SQLite 支持 ON CONFLICT DO UPDATE
(从 3.24 开始):
from sqlalchemy.dialects.sqlite import insertstmt = insert(YourModel).values(id=1, name='John Doe')
stmt = stmt.on_conflict_do_update(index_elements=[YourModel.id],set_=dict(name='John Doe')
)session.execute(stmt)
session.commit()
💡 小技巧:使用 add()
+ flush()
+ 捕获异常(不推荐用于高并发)
如果你不想用数据库特定语法,也可以尝试先 add,捕获唯一冲突异常后再 update:
try:session.add(obj)session.flush()
except IntegrityError:session.rollback()existing = session.query(YourModel).get(obj.id)for key, value in obj.__dict__.items():if not key.startswith('_'):setattr(existing, key, value)session.commit()
⚠️ 不推荐这种方式,因为并发时容易出错,且效率不高。
📝 总结
方法 | 数据库兼容性 | 推荐程度 |
---|---|---|
merge() | 兼容所有数据库 | ⭐⭐ |
on_conflict_do_update (PostgreSQL/SQLite) | PostgreSQL >= 9.5 / SQLite >= 3.24 | ⭐⭐⭐⭐ |
on_duplicate_key_update (MySQL) | MySQL | ⭐⭐⭐⭐ |
手动捕获异常 | 兼容性强 | ⭐ |