文章目錄
- ? 通用思路
- 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 | ???? |
手動捕獲異常 | 兼容性強 | ? |