事务必须显式开启(如BEGIN TRANSACTION)并配对COMMIT或ROLLBACK,否则单条DML语句自动提交;漏写COMMIT导致断连回滚,漏写ROLLBACK可能产生脏数据;嵌套事务仅计数,仅最后一个COMMIT真正生效。

事务不是自动开启的,必须显式控制,否则每条 INSERT 都是独立提交,失败一条就丢一条。
SQL Server 中 BEGIN TRANSACTION 必须手动配对 COMMIT 或 ROLLBACK
SQL Server 默认是自动提交模式(autocommit),单条 INSERT 执行完立刻生效。要让多条插入“全成功或全失败”,得先用 BEGIN TRANSACTION 开启事务,再用 COMMIT 或 ROLLBACK 显式结束。
- 漏写
COMMIT→ 连接断开时自动回滚,数据没存住 - 漏写
ROLLBACK在出错分支 → 错误后继续执行后续语句,可能插入脏数据 - 嵌套事务不被真正支持:多个
BEGIN TRANSACTION只增加计数,COMMIT只减计数,只有最后一个COMMIT才真正提交
推荐写法:
BEGIN TRY BEGIN TRANSACTION INSERT INTO orders (order_id, user_id, amount) VALUES (1001, 123, 299.99); INSERT INTO order_items (item_id, order_id, product_id) VALUES (2001, 1001, 5001); INSERT INTO order_items (item_id, order_id, product_id) VALUES (2002, 1001, 5002); COMMIT; END TRY BEGIN CATCH ROLLBACK; THROW; -- 重新抛出错误,避免静默失败 END CATCH
MySQL 的 START TRANSACTION 和 autocommit=0 差异
MySQL 默认也是 autocommit=1,但有两种等效方式关闭它:
-
START TRANSACTION:推荐,语义清晰,作用域为当前会话 -
SET autocommit = 0:全局影响当前连接,容易忘记恢复,不建议在长连接中混用
注意:DDL 语句(如 CREATE、ALTER)在 MySQL 中会隐式触发 COMMIT,哪怕你正在事务里 —— 这意味着不能把建表和插入包在同一事务中。
常见坑:
- 使用
INSERT ... ON DUPLICATE KEY UPDATE时,冲突触发更新也算作“影响行数 > 0”,不会报错,但事务仍继续;需靠业务逻辑判断是否真插入成功 -
INSERT IGNORE遇到主键/唯一冲突会静默跳过,也不报错,事务照样往下走
PostgreSQL 的事务块必须用 BEGIN / END 包裹
PostgreSQL 不允许在事务块外执行 DML,且每个事务必须以 COMMIT 或 ROLLBACK 显式终止。如果客户端异常断开,未完成的事务自动回滚。
批量插入时更关键的是:COPY 命令本身就是一个原子操作,但它不参与当前事务块 —— 即使你在 BEGIN 后调用 COPY,它也会立即提交(除非用 psql 的 \set AUTOCOMMIT off 模式)。所以:
- 要用事务包裹
COPY,得改用INSERT ... SELECT+ 临时表,或驱动端分批提交 -
executemany()在 psycopg2 中默认不开启事务,必须用conn.autocommit = False或显式with conn.cursor() as cur:配合conn.commit()
跨数据库事务失败时的回滚边界容易被忽略
事务只对当前连接生效,无法跨连接、跨进程、跨服务保证一致性。比如你用应用层循环发 10 条 INSERT,哪怕开了事务,只要中间网络中断或连接池回收,就只剩部分写入。
真正可靠的做法是:
- 把批量插入封装成单次 SQL 调用(如 SQL Server 的多值
VALUES、PostgreSQL 的INSERT ... SELECT UNNEST(...)) - 避免在事务里调用外部 API 或文件读写 —— 这些失败不会触发数据库回滚
- 高并发场景下,
SELECT ... FOR UPDATE加锁要谨慎,可能引发死锁,尤其涉及多表顺序不一致时
最常被跳过的点:没检查 @@ERROR(SQL Server)、ROW_COUNT()(MySQL)或 GET DIAGNOSTICS(PostgreSQL),导致表面成功、实际漏插却无感知。

















