PostgreSQL用on_conflict_do_nothing需加returning才能准确获取插入行数,rowcount返回实际插入数;MySQL的INSERT IGNORE的rowcount包含被忽略行;SQLite的rowcount恒为-1,须用前后计数差。

PostgreSQL 用 on_conflict_do_nothing 获取实际插入行数
PostgreSQL 是少数原生支持“忽略冲突并返回影响行数”的数据库。SQLAlchemy 1.4+ 通过 on_conflict_do_nothing 实现,但关键点在于:必须显式启用 returning 并调用 scalars().all() 或 fetchall(),否则 SQLAlchemy 默认不拉取结果集,rowcount 会是 -1。
实操建议:
- 使用
insert(...).on_conflict_do_nothing(index_elements=[...]),index_elements必须匹配唯一约束或索引字段(如['user_id']),不能写主键名id除非该列有唯一索引 - 加
.returning(table.c.id)(哪怕只为了占位)才能触发 PostgreSQL 返回受影响行;不加则execute().rowcount不可靠 - 执行后用
result.rowcount得到插入行数(不是返回值长度),这是最轻量方式;若还需新插入的 ID,再用result.scalars().all()
from sqlalchemy.dialects.postgresql import insertstmt = insert(User).values(users_data) stmt = stmt.on_conflict_do_nothing(index_elements=['email']) result = conn.execute(stmt.returning(User.id)) print("实际插入行数:", result.rowcount) # ✅ 正确
MySQL 用 INSERT IGNORE 或 REPLACE INTO 的 rowcount 行为差异
MySQL 没有标准 ON CONFLICT,SQLAlchemy 不直接封装 INSERT IGNORE,需手动拼语句或用 prefix_with。更关键的是:rowcount 含义和 PostgreSQL 不同——INSERT IGNORE 的 rowcount 包含被忽略的行,而 REPLACE INTO 是“删+插”,rowcount 可能为 2(删 1 + 插 1)。
实操建议:
- 用
insert(...).prefix_with("INSERT IGNORE"),但注意:SQLAlchemy 会把prefix_with当作字符串拼接,不校验语法,出错只能靠运行时报错 - 执行后
result.rowcount是“尝试插入的总行数”,无法直接分离“成功插入数”;若要精确计数,得先查已有 key,再做差集,再插入 - 避免用
REPLACE INTO,它会触发删除动作,可能破坏外键或触发器,且rowcount不反映“净新增”
stmt = insert(User).values(users_data).prefix_with("INSERT IGNORE")
result = conn.execute(stmt)
# result.rowcount == len(users_data),不管是否冲突 ✅ 但无区分能力
SQLite 用 ON CONFLICT IGNORE 时 rowcount 总是 -1 的原因
SQLite 支持 ON CONFLICT IGNORE,但 SQLAlchemy 的 SQLite 方言在执行批量插入时默认不启用 executemany 的行计数反馈,导致 result.rowcount 恒为 -1,即使底层 SQLite 实际执行了部分插入。
实操建议:
- 不要依赖
rowcount,改用事务前后查表计数:before = conn.execute(select(func.count()).select_from(User)).scalar(),插入后再查一次 - 若数据量小(insert().on_conflict_do_nothing()(SQLite 1.4+ 支持),每条都能拿到正确
rowcount - 确保表定义里有
sqlite_autoincrement或显式唯一约束,否则ON CONFLICT不生效
通用方案:用 merge() 替代“忽略插入”时的性能陷阱
session.merge() 看似能实现“存在则跳过”,但它本质是先 SELECT 再决定插入或更新,批量调用时 N+1 查询开销极大,1000 行可能触发 1000 次查询,比原生 INSERT IGNORE 慢两个数量级。
实操建议:
- 仅当必须走 ORM 生命周期(如需要触发
@validates或事件钩子)时才用merge,否则一律用 Core 的insert().on_conflict_*或原生语句 - 如果数据库不支持原生忽略(如旧版 MySQL),宁可先
SELECT id FROM table WHERE key IN (...)批量查重,再对差集执行insert,也比merge快 -
merge返回对象,不返回行数;想统计插入量,得自己维护一个计数器
实际插入行数这个需求,背后往往隐含“幂等写入+可观测性”的业务逻辑。不同数据库的 rowcount 定义根本不一致,硬套一个方法去适配所有方言,十有八九会在某个环境里翻车。最稳妥的做法,是按数据库类型分路径处理,而不是试图抽象出一个“通用忽略插入函数”。










