SQL事务无法部分回滚INSERT,必须显式控制粒度:原子性要求全提交或全回滚;INSERT报错默认中止事务但不自动回滚已执行语句,需靠SAVEPOINT、异常捕获并手动ROLLBACK或使用ON CONFLICT/IGNORE实现条件插入。

SQL事务中无法“部分回滚”INSERT,必须显式控制粒度
SQL标准事务是原子的:要么全部提交,要么全部回滚。所谓“部分INSERT失败后只回滚失败行”,本质是误解——INSERT语句一旦报错(如主键冲突、NOT NULL违例),默认会中止当前事务,但不会自动回滚已执行的成功语句;是否回滚取决于你有没有提前设好保存点或捕获异常并手动处理。
用SAVEPOINT实现插入过程中的条件性回滚
当一批INSERT中某些可能失败(例如批量导入用户数据时部分邮箱重复),又不想让整个事务失效,就得在每条或每组INSERT前设SAVEPOINT,并在出错后ROLLBACK TO它:
START TRANSACTION; INSERT INTO users (id, email) VALUES (1, 'a@b.com'); SAVEPOINT sp1; INSERT INTO users (id, email) VALUES (2, 'a@b.com'); -- 假设email唯一,此处失败 ROLLBACK TO sp1; INSERT INTO users (id, email) VALUES (2, 'c@d.com'); -- 换个值重试 COMMIT;
-
SAVEPOINT名不能重复,同一事务内多次使用需动态命名或覆盖 - MySQL 5.7+、PostgreSQL、SQL Server 支持;SQLite支持但不支持
RELEASE SAVEPOINT - 每个
SAVEPOINT会带来轻微开销,高频小插入不建议每行都设
用INSERT ... ON CONFLICT / INSERT IGNORE绕过失败而非回滚
如果目标只是“跳过非法数据继续插”,比手动管理SAVEPOINT更轻量:
PostgreSQL:
INSERT INTO users (id, email) VALUES (1, 'a@b.com'), (2, 'a@b.com') ON CONFLICT (email) DO NOTHING;
MySQL:
INSERT IGNORE INTO users (id, email) VALUES (1, 'a@b.com'), (2, 'a@b.com');
-
INSERT IGNORE会静默丢弃所有报错行(包括非唯一性错误,如数据截断),需谨慎 -
ON CONFLICT更精确,可指定冲突字段和动作(DO UPDATE也行) - 两者都不触发事务中止,适合“尽力插入”场景,但无法获取哪些行被跳过
存储过程中捕获错误并选择性重试需依赖具体数据库语法
想自动重试失败的INSERT(比如冲突时改ID再插),得靠存储过程+异常处理块。以PostgreSQL为例:
BEGIN
INSERT INTO users (id, email) VALUES (new_id, new_email);
EXCEPTION
WHEN unique_violation THEN
new_id := new_id + 1;
INSERT INTO users (id, email) VALUES (new_id, new_email);
END;- MySQL用
DECLARE EXIT HANDLER FOR SQLEXCEPTION,SQL Server用TRY...CATCH - 这类逻辑会让事务变重,且难以调试;生产环境建议前置校验(如先
SELECT查重) - 注意:异常处理块内若再出错,可能直接退出整个事务,未必能兜住
真正容易被忽略的是:多数ORM(如Django ORM、SQLAlchemy)默认不暴露底层SAVEPOINT或错误码细节,直接调用execute()或原生SQL才能精细控制。别指望.bulk_create(... ignore_conflicts=True)能覆盖所有业务回滚需求。

















