如何中止JDBC Postgresql CopyManager复制?

时间:2013-09-04 07:44:41

标签: java multithreading postgresql jdbc

有没有办法在单独的线程中调用copyIn()方法取消复制过程?

说,我有一个csv文件列表,我需要从中复制,获得最大的数据库服务器功率。所以我为n文件创建了n-threads-connections,但是如果选择了错误的文件,我找不到中止单个操作的方法。

杀死线程不起作用 - COPY只是继续运行。

FutureTask<>类用于创建线程,因此有一个列表 - 每个csv一个。

调用task.cancel(true)在服务器上的复制过程方面没有任何作用。只有System.exit()可以用火来杀死它。

有什么想法吗?

我的一些代码:

Uploader.java implements Callable

public static long uploadFile(final File file, final String tableName) {

    long status = 0;
    try {
        CopyManager copyManager = 
           new CopyManager((BaseConnection) new DataSource().connect());
        FileReader reader = new FileReader(file);
        status = copyManager.copyIn(sql, reader);
    } catch (SQLException | IOException e) {
       ...
    }
    return status;
}

@Override
public Long call() throws Exception {
    return uploadFile(file, tableName);
}

Upload files method body

for (File file : files) {
        FutureTask<Long> ftask =
                new FutureTask<>(
                        new Uploader(defaultTableName, file)
                );
        tasks.add(ftask);
        execService.execute(ftask);
    }

解决:

找到了解决方案,但它需要对代码进行一些更改。

Upload files method body现在看起来像这样

for (File file : files) {
    Uploader uploader = new Uploader(defaultTableName, file);
    uploaders.add(uploader);
    Future<Long> f = execService.submit(uploader);

    //save the Future to get the copy result when finished

}

有了这个,我们可以轻松地调用一些Uploader的方法,在这种方法中可以关闭数据库连接并正确处理异常。它将停止在服务器上复制。

我接受解决方案可能不是最优雅的解决方案,但它可以正常运行,工作速度快,而且不需要太多代码。

2 个答案:

答案 0 :(得分:2)

PostgreSQL实际上不支持带内查询取消。

当您从JDBC驱动程序请求取消查询时,它会使新连接发送取消消息。 (这意味着如果你在max_connections取消将失败,这有点不正常。)

这样做的结果是你可以自己做同样的事情:

  • 在开始复制操作之前,使用pg_backend_pid()获取工作人员的进程ID;

  • 如果要取消副本,请打开新连接并使用之前记录的pid发出pg_cancel_backend(?)。如果它没有停止,您可以稍等一下,然后执行pg_terminate_backend(?)

这些是普通的SQL级函数。

唯一真正的问题是取消和终止请求是会话级而不是语句级别。因此,他们可以通过声明完成和新声明的开始进行竞赛,例如:

  • client1:COPY开始
  • client2:连接发送取消消息
  • client1:复制完成
  • client1:新的单独副本启动
  • client2发送pg_cancel_backend(...)

此时,第二个副本将被终止,这可能不是您想要的。因此,您必须确保使用适当的排除客户端来防止这种情况发生,确保在开始新语句之前完成任何未完成的取消请求。

IIRC JDBC驱动程序内部存在同样的问题。这是团队真正想要一种方法来取消特定的每会话语句序列号的原因之一,就像一个(当前不存在的)pg_cancel_backend(pid, statementnumber)如果语句已经终止而中止错误,而不是无论如何发送取消。

答案 1 :(得分:1)

免责声明:我没有试过这个,我只是通过查看源代码了解了这个想法

CopyManager.copyIn(String sql)方法返回CopyIn接口的实例,后者又是CopyOperation的后代。该接口具有cancelCopy()方法。

请在此处查看JavaDoc:http://jdbc.postgresql.org/documentation/publicapi/org/postgresql/copy/CopyOperation.html#cancelCopy%28%29

但是,采用流来复制数据的方法只返回一个long值,因此无法使用那里使用的CopyOperation实例。

但是,在查看copyIn()方法的源代码时,这似乎很容易实现。

因此,您不必调用copyIn(String, Reader),而是在代码中使用该方法中的代码:

// your code 
CopyManager copyManager = 
       new CopyManager((BaseConnection) new DataSource().connect());
FileReader from = ...  // different name!
int bufferSize = 65536;

// here starts the copy of the driver's implementation of the copyIn() method.

char[] cbuf = new char[bufferSize];
int len;

// if you store the instance of the CopyIn interface in an instance variable you 
// should be able to call cancelCopy() on it
CopyIn cp = copyManager.copyIn(sql);  

try {
    while ( (len = from.read(cbuf)) > 0) {
        byte[] buf = encoding.encode(new String(cbuf, 0, len));
        cp.writeToCopy(buf, 0, buf.length);
    }
    return cp.endCopy();
} finally { // see to it that we do not leave the connection locked
    if(cp.isActive())
        cp.cancelCopy();
}