Mysql或Oracle迁移到Postgresql系产品后,经常会发生事务回滚导致的问题,具体问题一般都是类似于:
为什么我没rollback,我的事务就自己回滚了?
下面我举一个简单的例子,说明下PG和其他两款DB在事务回滚行为上的差异
Oracle事务内报错后的行为
(完整代码贴在文章最后)
Class.forName("oracle.jdbc.driver.OracleDriver");
conn = DriverManager.getConnection(URL, USER, PASSWORD);
conn.setAutoCommit(false);
Statement stmt = conn.createStatement();
/* 事务启动,写入一些数据 */
stmt.executeUpdate("INSERT INTO t1 VALUES (1)");
/* 查询t1全部数据 */
selectAllFromTable(stmt, "写入后查询");
/* 制造一些错误,这里查询不存在的列 */
try {
stmt.executeQuery("select xxxxxxx from t1 ");
} catch (Exception e) {
/* 那么到这里事务是否已经回滚了?*/
selectAllFromTable(stmt, "异常后查询");
}
conn.commit();
selectAllFromTable(stmt, "提交后查询");
...
private static void selectAllFromTable(Statement stmt, String info) throws SQLException {
System.out.println("=======" + info + "=======");
ResultSet rs1 = stmt.executeQuery("SELECT i FROM t1");
ResultSetMetaData rsMetaData1 = rs1.getMetaData();
printRs(rs1, rsMetaData1);
}
...
建表语句
create table t1 (i int);
我们可以猜一下三次selectAllFromTable(函数就是简单的查全表)输出会是什么
用Mysql或Oracle的同学可能直接就可以想到:
=======写入后查询=======
I 1
=======异常后查询=======
I 1
=======提交后查询=======
I 1
这里关键就是 出现查询异常后,不影响事务的正常运行,后面可以继续在事务内操作。但在PG中就不一样了。
Postgresql事务内报错后的行为
public class TestPgsql {
private static final String URL = "jdbc:postgresql://121.196.26.196:7001/postgres";
private static final String USER = "postgres";
private static final String PASSWORD = "333";
public static void main(String[] args) {
Connection conn = null;
try {
Class.forName("org.postgresql.Driver");
conn = DriverManager.getConnection(URL, USER, PASSWORD);
conn.setAutoCommit(false);
Statement stmt = conn.createStatement();
/* 事务启动,写入一些数据 */
stmt.executeUpdate("INSERT INTO t1 VALUES (1)");
/* 查询t1全部数据 */
selectAllFromTable(stmt, "写入后查询");
/* 制造一些错误,这里查询不存在的列 */
try {
stmt.executeQuery("select xxxxxxx from t1 ");
} catch (Exception e) {
/* 那么到这里事务是否已经回滚了?*/
selectAllFromTable(stmt, "异常后查询");
}
conn.commit();
selectAllFromTable(stmt, "提交后查询");
...
这里就不再贴报错了,我贴下单步调试的过程更容易理解
第一个差异点:事务内SQL报错后,再执行任何语句都会抛异常
在报错后的事务内再执行查询,报PG的标准错误:org.postgresql.util.PSQLException: ERROR: current transaction is aborted, commands ignored until end of transaction block
第二个差异点:报错后,事务自动回滚,会话状态处于idle in transaction (aborted)
那么在SQL报错后,为了之前的修改能生效,我在报错后的异常处理时直接提交可以吗?
不可以,在报错时事务已经回滚,虽然提交没有报错,但是写入的数据不会生效
commit后
数据没有写入:
迁移到Postgresql后如何改造?
方案一:PL/pgSQL
使用Postgresql提供的PL/pgSQL语法,将相关逻辑写入PG的函数中,使用PG的EXCEPTION语法封装响应的处理逻辑,在业务代码中调用函数即可保证事务不会中断。
https://www.postgresql.org/docs/9.1/plpgsql-control-structures.html#PLPGSQL-ERROR-TRAPPING
[ <<label>> ]
[ DECLARE
declarations ]
BEGIN
statements
EXCEPTION
WHEN condition [ OR condition ... ] THEN
handler_statements
[ WHEN condition [ OR condition ... ] THEN
handler_statements
... ]
END;
If no error occurs, this for
方案二:寻找替代逻辑,避免事务内产生错误
例如这样的业务逻辑(来自云上客户):
On Oracle伪代码
try
select xxx from t1
xxx列存在的处理逻辑
catch
xxx列存不存在的处理逻辑
那么到Postgresql可以采用无异常的处理方式:
On Postgresql伪代码
int n = select count(*) from information_schema.columns WHERE table_schema = 'postgres' and table_name = 't1' and column_name = 'xxx';
if (n == 0) {
xxx列存不存在的处理逻辑
} else {
xxx列存在的处理逻辑
}