bigywater.tech
Java · 316

JDBC Savepoint:在一个事务中局部回滚

事务里一条语句失败了,整个事务还能继续吗?PostgreSQL 与 MySQL 为什么不一样?保存点怎么救?嵌套保存点的规则是什么?pgjdbc 的 autosave 值得开吗?这篇逐个触发。

25P02
PostgreSQL 事务里语句失败后,后续语句收到的 SQLState(MySQL 没有这个问题)
564.9 → 1111.3 ms
pgjdbc autosave=always 对 20000 行逐行插入的影响
S1C00
MySQL 驱动里对无名保存点调用 getSavepointId() 的 SQLState

PostgreSQL 出错与保存点

实测 · 本机执行输出
1 重复主键的 INSERT 失败:PSQLException(23505) ERROR: duplicate key value violates unique constraint "t_pkey"
  失败之后同一个事务里的下一条 INSERT:PSQLException(25P02) ERROR: current transaction is aborted, commands ignored until end of transaction block
  commit():没有报错
  最终表里的 id:[]
2 在保存点之后重复主键失败:PSQLException(23505) ERROR: duplicate key value violates unique constraint "t_pkey"
  rollback(sp) 之后继续:
  提交后表里的 id:[1, 2](第一次的 1 与恢复之后的 2 都保留)

PostgreSQL 保存点规则

实测 · 本机执行输出
3 三次插入、两个保存点之后,事务里的 id:[1, 2, 3]
  rollback(s2) 之后:[1, 2]
  再 insert 4,然后 rollback(s1) 之后:[1]
  再 rollback(s2):PSQLException(3B001) ERROR: savepoint "s2" does not exist
  释放之后再 rollback(s1):PSQLException(25P02) ERROR: current transaction is aborted, commands ignored until end of transaction block
  提交后表里的 id:[]
4 两个同名保存点 same:rollback(第二个)之后表里 []
5 无名保存点 getSavepointId() = 0
  无名保存点 getSavepointName():PSQLException(42809) Cannot retrieve the name of an unnamed savepoint.
  命名保存点 getSavepointName() = x
  命名保存点 getSavepointId():PSQLException(42809) Cannot retrieve the id of a named savepoint.
6 autocommit=true 时 setSavepoint:PSQLException(25P01) Cannot establish a savepoint in auto-commit mode.
7 commit 之后 rollback(旧保存点):PSQLException(25P01) ERROR: ROLLBACK TO SAVEPOINT can only be used in transaction blocks
  supportsSavepoints() = true

MySQL 出错与保存点

实测 · 本机执行输出
1 重复主键的 INSERT 失败:SQLIntegrityConstraintViolationException(23000) Duplicate entry '1' for key 't.PRIMARY'
  失败之后同一个事务里的下一条 INSERT:成功
  commit():没有报错
  最终表里的 id:[1, 2]
2 在保存点之后重复主键失败:SQLIntegrityConstraintViolationException(23000) Duplicate entry '1' for key 't.PRIMARY'
  rollback(sp) 之后继续:
  提交后表里的 id:[1, 2](第一次的 1 与恢复之后的 2 都保留)

MySQL 保存点规则

实测 · 本机执行输出
3 三次插入、两个保存点之后,事务里的 id:[1, 2, 3]
  rollback(s2) 之后:[1, 2]
  再 insert 4,然后 rollback(s1) 之后:[1]
  再 rollback(s2):SQLSyntaxErrorException(42000) SAVEPOINT s2 does not exist
  releaseSavepoint(s1):成功
  释放之后再 rollback(s1):SQLSyntaxErrorException(42000) SAVEPOINT s1 does not exist
  提交后表里的 id:[1]
4 两个同名保存点 same:rollback(第二个)之后表里 []
5 无名保存点 getSavepointId():SQLException(S1C00) Only named savepoints are supported.
  无名保存点 getSavepointName() = e4e697bb_3f78_4cf1_82fa_e388322a4f26
  命名保存点 getSavepointName() = x
  命名保存点 getSavepointId():SQLException(S1C00) Only named savepoints are supported.
6 autocommit=true 时 setSavepoint:没有报错
7 commit 之后 rollback(旧保存点):SQLSyntaxErrorException(42000) SAVEPOINT old does not exist
  supportsSavepoints() = true

autosave 默认

实测 · 本机执行输出
8 重复主键失败:PSQLException(23505) ERROR: duplicate key value violates unique constraint "t_pkey"
  失败之后的下一条 INSERT:PSQLException(25P02) ERROR: current transaction is aborted, commands ignored until end of transaction block
  最终表里的 id:[]
  逐行插入 20000 行(一个事务):564.9 ms

autosave=always

实测 · 本机执行输出
8 重复主键失败:PSQLException(23505) ERROR: duplicate key value violates unique constraint "t_pkey"
  失败之后的下一条 INSERT:成功(驱动在语句之前自动设了保存点)
  最终表里的 id:[1, 2]
  逐行插入 20000 行(一个事务):1111.3 ms

autosave=conservative

实测 · 本机执行输出
8 重复主键失败:PSQLException(23505) ERROR: duplicate key value violates unique constraint "t_pkey"
  失败之后的下一条 INSERT:PSQLException(25P02) ERROR: current transaction is aborted, commands ignored until end of transaction block
  最终表里的 id:[]
  逐行插入 20000 行(一个事务):572.4 ms

分步演示

JDBC 保存点
本步的对象

    本文用到的工具与边界

    • 本机 macOS 27 arm64 / OpenJDK(Temurin 17.0.20、Homebrew 21.0.12 与 27,每篇注明实际用的版本);用 javac 编译、java 直接运行,需要第三方库时用 Maven 解析依赖;计时用 System.nanoTime,数字每次运行会有小幅抖动。JDK 21.0.12;自己起的 PostgreSQL 16.15(端口 9501)与 MySQL 26.7.0(端口 9601),用户自己的数据库没有被使用;pgjdbc 42.7.4、mysql-connector-j 9.1.0
    • 没有验证:保存点对行锁的影响(回滚到保存点后锁是否释放)、嵌套层数很多时的开销、PostgreSQL 子事务对性能的长期影响(如 subtransaction 溢出)、autosave=conservative 在其它失败类型下的恢复行为、XA / 分布式事务里的保存点
    京ICP备2026067502号-1京公网安备11010502063487号