将多个查询作为一个 dblink 事务执行

Execute multiple queries as one dblink transaction

我在一个 Java 应用程序中工作,我需要同时执行这两个查询(作为 java 中的字符串)并在出现错误时回滚事务。

SELECT dblink_exec('hostaddr=xxx.xx.xxx.xxx port=5432 dbname=bdname user=myuser password=mypass connect_timeout=2',
'INSERT INTO table3(field4) 
VALUES (5)') AS result;

SELECT dblink_exec('hostaddr=xxx.xx.xxx.xxx port=5432 dbname=bdname user=myuser password=mypass connect_timeout=2',
'UPDATE table1 SET field2 = field2 + 3.0 WHERE field1 = 16436') AS result;

更新

我用 ; 分隔的两个查询创建了一个字符串,正如评论中建议的

更新

我已经尝试 JDBC 原子事务作为 java 中的代码。我强制第二个 sql 失败,但即使我指定 .setAutoCommit(false); dblink 影响了第一个查询的另一个数据库。我在没有 dblink 事务的情况下尝试了相同的代码,回滚效果很好。 dblink 是问题所在。

Java更新

public static boolean ejecutarTransaccionDblink(String sql) {
    boolean estado = false;
    try {
        Statement sentencia = conexion.createStatement();
        conexion.setAutoCommit(false);
        if (sql.length() != 0) {
            if (sentencia.execute(sql)) {
                conexion.commit();
                estado = true;
            }
        }
    } catch (SQLException ex) {
        System.out.println(ex.toString());
        try {
            estado = false;
            conexion.rollback();
        } catch (SQLException ex1) {
        }
    } finally {
        try {
            conexion.setAutoCommit(true);
            return estado;
        } catch (SQLException ex) {
            return estado;
        }
    }
}

感谢您的帮助。

为了 运行 事务中的查询,您只需在连接上将 auto-commit 功能设置为 false(记得在完成后将其设置回 true,特别是如果连接是从连接池中检索的 - 因此被重用)。

代码比较简单:

ResultSet resultado = null;
String statement1 = "SELECT dblink_exec('hostaddr=xxx.xx.xxx.xxx port=5432 dbname=bdname user=myuser password=mypass connect_timeout=2','INSERT INTO table3(field4) VALUES (5)') AS result";
String statement2 = "SELECT dblink_exec('hostaddr=xxx.xx.xxx.xxx port=5432 dbname=bdname user=myuser password=mypass connect_timeout=2','UPDATE table1 SET field2 = field2 + 3.0 WHERE field1 = 16436') AS result";
    try {
        // set auto-commit to false, to indicate start of transaction
        conexion.setAutoCommit(false);

        // run whatever queries you want on the connection, in a transaction, e.g. :
        Statement sentencia = conexion.createStatement();
        resultado = sentencia.executeQuery(sql);

        //manually commit the transaction when you're done
        conexion.commit();

        return resultado;
    } catch (SQLException ex) {
        System.out.println("Error Consulta:" + ex);

        // ensure transaction is rolled-back in case of error. (note: you might want to add an NPE check here
        con.rollback();
        return null;
    } finally {
        // close any statements / preparedStatements, etc. Note you MUST do this in the finally block, to ensure your connection won't stay in transaction.
        con.setAutoCommit(true);
    }

希望对您有所帮助

更新

正如@a_horse_with_no_name 指出的那样,dblink_exec 连接到一个远程数据库,因此上面的内容并不完整,因为它只处理第一个数据库中的事务。

我相信答案应该在于使用 命名连接 dblink_exec 其中过程涉及:

  • 正在打开与 dblink_connect
  • 的新连接
  • 正在与 dblink_exec
  • 的新命名连接中开始事务
  • 正在先前打开的连接中使用 dblink_exec 执行查询 1
  • 正在先前打开的连接中使用 dblink_exec 执行查询 2
  • 在先前打开的连接中提交事务

因此,代码将如下所示:

SELECT dblink_connect('myconn','hostaddr=xxx.xx.xxx.xxx port=5432 dbname=bdname user=myuser password=mypass connect_timeout=2');
SELECT dblink_exec('myconn','BEGIN');
SELECT dblink_exec('myconn', 'INSERT INTO table3(field4) VALUES (5)');
SELECT dblink_exec('myconn', 'UPDATE table1 SET field2 = field2 + 3.0 WHERE field1 = 16436');
SELECT dblink_exec('myconn','COMMIT');

问题是,这一切都未经测试,所以@KazMiller 你能试一试吗?

如果所有其他方法都失败了,使用一个或多个 CTE:

将多个 SQL 命令链接成一个命令
WITH upd AS (
  UPDATE table1 SET field2 = field2 + 3.0 WHERE field1 = 16436
  )
INSERT INTO table3(field4) 
VALUES (5)') AS result;

INSERT第一,无所谓。通常,以这种方式链接两个不相关的命令是没有意义的,但它是该功能的一个干净的应用程序。您可以通过这种方式链接任意数量的命令。您不能让两个命令写入同一个 。你甚至可以有一个最终的 SELECT return 相关或不相关的值。与 CTE 中的 SELECT 不同,所有数据修改 CTE 始终执行到完成。 The manual:

Data-modifying statements in WITH are executed exactly once, and always to completion, independently of whether the primary query reads all (or indeed any) of their output. Notice that this is different from the rule for SELECT in WITH: as stated in the previous section, execution of a SELECT is carried only as far as the primary query demands its output.

相关:

  • PostgreSQL: using foreign keys, delete parent if it's not referenced by any other child

另一种选择是在目标服务器上创建一个 函数LANGUAGE sqlLANGUAGE plpgsql - 但任何语言都应该这样做)以封装任何数字单个事务中的命令数:

CREATE OR REPLACE FUNCTION f_wrapper()
  RETURNS void AS
$func$
   UPDATE table1 SET field2 = field2 + 3.0 WHERE field1 = 16436;
   INSERT INTO table3(field4) VALUES (5);
$func$ LANGUAGE sql;

然后:

SELECT dblink_exec('hostaddr=xxx.xx.xxx.xxx port=5432 dbname=bdname user=myuser password=mypass connect_timeout=2',
'SELECT f_wrapper()') AS result;

您可以即时创建(和删除)一个函数或保留一个采用参数作为值的函数。