SQLite OR FAIL 与 ABORT:一条插入报错,前面写进去的行为什么还在

前天 3阅读

一次多行插入遇到重复编号,应用收到 IntegrityError,便认为这条语句一行也没写入。使用默认的 ABORT 时这个判断通常符合本例,但如果 SQL 明确写了 OR FAIL,失败点之前的成功修改会留下来。只捕获异常而不检查数据状态,可能让重试过程重复处理已经完成的一部分。

下面分别建立两个内存数据库,在同一事务中先插入编号九,再运行包含重复编号的多行语句。保存为 demo.py,运行 python demo.py;实验结束主动回滚并关闭连接,不接触任何已有数据库。

SQLite OR FAIL 与 ABORT:一条插入报错,前面写进去的行为什么还在

AI生成概念插图:遇到停止标记后,一条路径保留已完成行,另一条路径撤回当前语句的修改;不是数据库界面截图。

import sqlite3

def run_case(mode):
    assert mode in ("ABORT", "FAIL")
    db = sqlite3.connect(":memory:", isolation_level=None)
    try:
        db.execute("CREATE TABLE item(id INTEGER PRIMARY KEY)")
        db.execute("BEGIN")
        db.execute("INSERT INTO item VALUES (9)")
        try:
            db.execute(
                f"INSERT OR {mode} INTO item VALUES (1), (2), (2), (3)"
            )
        except sqlite3.IntegrityError:
            pass
        else:
            raise AssertionError("duplicate unexpectedly accepted")
        rows = [r[0] for r in db.execute("SELECT id FROM item ORDER BY id")]
        expected = [9] if mode == "ABORT" else [1, 2, 9]
        assert rows == expected
        assert db.in_transaction
        print(mode, "after error:", rows, "active:", db.in_transaction)
        db.execute("ROLLBACK")
        assert list(db.execute("SELECT id FROM item")) == []
        print(mode, "after rollback: []")
    finally:
        db.close()

run_case("ABORT")
run_case("FAIL")

异常相同,留下的状态不同

ABORT 分支输出 [9]:编号一和二属于失败的当前语句,因此被撤回;编号九来自同一事务中的上一条成功语句,所以仍然存在。FAIL 分支输出 [1, 2, 9]:冲突发生时停止继续插入,但不撤回该语句先前成功的两行。冲突之后的编号三也没有被处理。

这里主动使用 ORDER BY 只为稳定显示结果,不依赖数据库碰巧返回的顺序。两组输入使用完全相同的主键约束,差别仅在固定的冲突策略。代码中的字符串插值只接受断言白名单中的两个策略名;真实应用不能把未经约束的用户输入直接拼进 SQL。

语句结束,不表示事务结束

两次异常后 in_transaction 都为 True。ABORT 撤回当前语句,FAIL 留下已执行部分,但它们在本例中都没有替应用决定整个事务的最终去向。随后显式执行 ROLLBACK,编号九也一起消失,因为它和后续插入仍属于同一笔尚未提交的事务。

如果业务要求这一批导入全部成功或全部取消,应在事务边界明确处理失败,并核验回滚后的状态。不要在捕获 IntegrityError 后直接继续 COMMIT,然后向调用方只返回一个笼统的失败提示;这样保存的数据和对方理解的结果可能不同。需要允许部分成功时,则应记录哪些行真正保留。

这份实验刻意使用单条 INSERT 的多个 VALUES。把它换成 Python 的 executemany,会变成多次执行语句,错误边界也随之改变,不能继续套用“当前语句里的前两行”解释。排查前先确认底层发送了几条 SQL,以及事务从哪一行开始。

本文验证的是主键唯一性冲突。外键错误有不同限制,不能假设 OR FAIL 会保留同样的部分结果。也不要把 FAIL 理解成 IGNORE:FAIL 会报错并停止这条语句,IGNORE 对适用的约束可能跳过冲突行并继续。选择策略应依据可接受的数据状态,而不只是希望错误消息消失。

资料核对日期:2026年10月2日。代码在本地 Python 3.12.14、SQLite 3.53.1 实际运行并通过断言;结果只对应文中给定输入。

参考资料

文章版权声明:除非注明,否则均为云鹊BLOG原创文章,转载或复制请以超链接形式并注明出处。