SQLAlchemy插入CSV至UNIQUE字段时忽略重复值并正常提交问题
问题根因
该问题由数据库事务的原子性特性导致:同一事务内任意一条SQL触发UNIQUE约束类错误时,整个事务会被标记为待回滚的失效状态。仅通过try/except捕获异常不做事务状态重置的话,后续所有数据库操作都无法正常执行,最终执行commit时整个事务会被整体回滚,此前插入成功的非重复数据也会全部丢失。
另外代码中使用裸except:直接pass的写法会吞掉所有类型的报错,出现连接异常、字段匹配错误等问题时完全无法排查。
解决方案
根据使用的数据库类型和场景,二选一即可:
方案1:数据库层自动跳过冲突(推荐,性能最优)
直接使用数据库原生的冲突处理语法,不需要在Python层做异常捕获,插入时遇到重复数据会被数据库自动忽略,批量处理性能远高于Python层循环捕获。
- PostgreSQL/SQLite(3.24及以上版本)使用
ON CONFLICT DO NOTHING语法:
db.execute(""" INSERT INTO authors1 (name) VALUES (:name) ON CONFLICT (name) DO NOTHING """, {"name": row['author']})
- MySQL使用
INSERT IGNORE语法:
db.execute("INSERT IGNORE INTO authors1 (name) VALUES (:name)", {"name": row['author']})
替换原代码中的插入语句后,原有循环逻辑不需要调整,所有重复数据会被自动过滤,循环结束后正常执行db.commit()即可提交全部有效数据。
方案2:Python层捕获异常+保存点回滚
如果使用的数据库不支持上述原生冲突跳过语法,可以通过SQLAlchemy的嵌套事务(本质是数据库保存点)实现单条插入失败的局部回滚,不会影响事务内此前已执行成功的操作。
修改后的完整可运行代码:
import csv import os from sqlalchemy import create_engine from sqlalchemy.orm import scoped_session, sessionmaker # 精准导入约束冲突异常类,禁止使用裸except from sqlalchemy.exc import IntegrityError engine = create_engine(os.getenv("DATABASE_URL")) db = scoped_session(sessionmaker(bind=engine)) def main(): with open("books.csv") as file: reader = csv.DictReader(file) for row in reader: try: # 创建单条插入对应的保存点 savepoint = db.begin_nested() db.execute( "INSERT INTO authors1 (name) VALUES (:name)", {"name": row['author']} ) # 单条插入成功则提交保存点 savepoint.commit() print(f"added {row['author']}") except IntegrityError: # 触发唯一约束冲突时,仅回滚当前保存点,事务恢复可用状态 savepoint.rollback() print(f"skip duplicate author: {row['author']}") # 循环结束后提交全量有效数据 db.commit() if __name__ == "__main__": main()
注意事项
- 不要在捕获异常后直接调用
db.rollback(),这会回滚整个事务,导致之前已经插入成功的所有数据被清空,必须通过嵌套事务/保存点实现局部回滚。 - CSV数据量较大时优先选择方案1,数据库层处理冲突的效率比Python层逐行捕获异常高1~2个数量级。
内容的提问来源于stack exchange,提问作者Francisco Gutierrez Ramirez
相关产品推荐
相关产品推荐

