java – 以极高的速度提取行

我在Oracle中有非常大的表(数亿行,包含数字和字符串),我需要读取该表的所有内容,格式化它并写入文件或任何其他资源.
通常我的解决方案如下所示:

package my.odp;

import java.io.BufferedWriter;
import java.io.File;
import java.io.FileWriter;
import java.util.concurrent.ArrayBlockingQueue;
import java.util.concurrent.TimeUnit;
import java.lang.Throwable;
import java.sql.*;


public class Main {
public static volatile boolean finished = false;

public static void main(final String[] args) throws InterruptedException {
    final ArrayBlockingQueue<String> queue = new ArrayBlockingQueue<String>(10000);
    final Thread writeWorker = new Thread("ODP Writer") {
        public void run() {
            try {
                File targetFile = new File(args[0]);
                FileWriter fileWriter = new FileWriter(targetFile);
                BufferedWriter writer = new BufferedWriter(fileWriter);
                String str;
                try {
                    while (!finished) {
                        str = queue.poll(200, TimeUnit.MILLISECONDS);
                        if (str == null) {
                            Thread.sleep(50);
                            continue;
                        }
                        writer.write(str);
                        writer.write('\n');
                    }
                } catch (InterruptedException e) {
                    writer.close();
                    return;
                }
            }
            catch (Throwable e) {
                e.printStackTrace();
                return;
            }
        }
    };

    final Thread readerThread = new Thread("ODP Reader") {
        public void run() {
            try {
                Class.forName("oracle.jdbc.OracleDriver");
                Connection conn = DriverManager.getConnection("jdbc:oracle:thin:@//xxx.xxx.xxx.xxx:1521/orcl", "user", "pass");

                Statement stmt = conn.createStatement(ResultSet.TYPE_FORWARD_ONLY, ResultSet.CONCUR_READ_ONLY);
                stmt.setFetchSize(500000);
                ResultSet rs = stmt.executeQuery("select * from src_schema.big_table_view");
                System.out.println("Fetching result");
                while (rs.next()) {
                    StringBuilder sb = new StringBuilder();
                    sb.append(rs.getString(1)).append('\t');//OWNER
                    sb.append(rs.getString(2)).append('\t');//OBJECT_NAME
                    sb.append(rs.getString(3)).append('\t');//SUBOBJECT_NAME
                    sb.append(rs.getLong(4)).append('\t');//OBJECT_ID
                    sb.append(rs.getLong(5)).append('\t');//DATA_OBJECT_ID
                    sb.append(rs.getString(6)).append('\t');//OBJECT_TYPE
                    sb.append(rs.getString(7)).append('\t');//CREATED
                    sb.append(rs.getString(8)).append('\t');//LAST_DDL_TIME
                    sb.append(rs.getString(9)).append('\t');//TIMESTAMP
                    sb.append(rs.getString(10)).append('\t');//STATUS
                    sb.append(rs.getString(11)).append('\t');//TEMPORARY
                    sb.append(rs.getString(12)).append('\t');//GENERATED
                    sb.append(rs.getString(13)).append('\t');//SECONDARY
                    sb.append(rs.getString(14)).append('\t');//NAMESPACE
                    sb.append(rs.getString(15));//EDITION_NAME
                    queue.put(sb.toString());
                }

                rs.close();
                stmt.close();
                conn.close();
                finished = true;
            } catch (Throwable e) {
                e.printStackTrace();
                return;
            }
        }
    };
    long startTime = System.currentTimeMillis();
    writeWorker.start();
    readerThread.start();
    System.out.println("Waiting for join..");
    writeWorker.join();
    System.out.println("Exit:"+ (System.currentTimeMillis() - startTime));
}

}

有两个线程:一个用于从结果集中获取行,另一个用于写入字符串值.测量的加载速度大约是10Mb / s,在我的情况下,我需要加快10倍.
Profiler显示最耗时的方法是

oracle.jdbc.driver.OracleResultSetImpl.getString()

oracle.net.ns.Packet.receive()

您是否有任何想法如何使jdbc更快地加载数据?
任何关于查询优化,字符串加载优化,调整JDBC驱动程序或使用另一个,直接使用oracle JDBC实现,调整Oracle的想法都值得赞赏.

更新:
我编译并列出了下面的讨论结果:

>我无法访问DBMS服务器,除了连接到Oracle数据库和服务器无法连接到任何外部资源.使用服务器或远程文件系统的任何转储和提取工具都无法应用,也无法在服务器上安装和使用任何外部Java或PL / SQL例程.只有连接执行查询 – 这就是全部.
>我使用了探查器并挖掘了Oracle JDBC驱动程序.我发现最昂贵的操作是读取数据,即Socket.read().所有字符串字段都表示为一个char数组,并且对性能几乎没有影响.通常,我使用分析器检查整个应用程序,而Socket.read()绝对是最昂贵的操作.提取字段,构建字符串,写入数据几乎不消耗任何东西.问题仅在于阅读数据.
>服务器端数据表示的任何优化都没有实际效果.连接字符串和转换时间戳对性能没有影响.
> App被重写为有几个读者线程,它们将准备好的数据放入编写器队列中.每个线程都有自己的连接,没有使用池,因为它们减慢了提取速度(我使用了oracle推荐的UCP池,它占用了大约10%的执行时间,所以我放弃了它).结果集fetchSize也增加了,因为从默认值(10)切换到50000可以提高50%的性能增长.
>我测试了多线程版本如何与4个读取线程一起工作,并发现增加读者数量只会减慢提取速度.
我尝试启动2个实例,其中每个实例都有两个读取器,并且两个实例都与单个实例同时工作,即双数据提取需要与单个实例相同的时间.不知道为什么会发生这种情况,但看起来oracle驱动程序有一些性能限制.具有4个独立连接的应用程序比2个具有2个连接的App实例慢.
(Profiler用于确保驱动程序的Socket.read()仍然是主要问题,所有其他部分在多线程模式下工作正常).
>我尝试使用SAS获取所有数据,并且它可以比JDBC快2倍地执行相同的提取,两者都使用单个连接到Oracle并且不能使用任何转储操作. Oracle确保JDBC瘦驱动程序与本机驱动程序一样快.

也许Oracle有另一种方法可以通过ODBC或其他方式快速提取到远程主机?

最佳答案
假设您已经检查过接口,防火墙,代理等基本网络内容,以及数据库服务器的硬件元素.

选项1 :

代替 :

Class.forName("oracle.jdbc.OracleDriver");
Connection conn = DriverManager.getConnection("jdbc:oracle:thin:@//xxx.xxx.xxx.xxx:1521/orcl", "user", "pass");

尝试使用:

OracleDataSource ods = new OracleDataSource();
java.util.Properties prop = new java.util.Properties();
prop.setProperty("MinLimit", "2");
prop.setProperty("MaxLimit", "10");
String url = "jdbc:oracle:oci8:@//xxx.xxx.xxx.xxx:1521/orcl";
ods.setURL(url);
ods.setUser("USER");
ods.setPassword("PWD");
ods.setConnectionCachingEnabled(true);
ods.setConnectionCacheProperties (prop);
ods.setConnectionCacheName("ImplicitCache01");

更多细节here

选项2:Fetchsize

正如Stephen强烈指出的那样,fetchsize似乎太大了.

而且,对于50,000的获取大小,你的-Xms和-Xmx是多少.另外,在分析器中,最大堆大小是多少?

选项3:DB

>检查src_schema.big_table_view的索引和查询计划
>这是一个工具还是一个应用系统.如果只是一个工具,你可以
基于数据库系统添加并行度,索引提示,分区等
功能

选项4:线程

说n<应用程序服务器上的核心数 您可以启动n个编写器的线程,每个线程都配置为处理某个存储桶,例如thread1处理0到10000,写入n个不同的文件,一旦完成所有的文件,后加入,最好使用低级OS命令将文件合并在一起. 也就是说,所有这些都不应该像现在这样预定义代码. ‘n’和桶应该在运行时计算.创建多个线程而不是系统支持的线程只会搞砸. 选项5: 代替

select * from src_schema.big_table_view

你可以用

SELECT column1||CHR(9)||column2||CHR(9).....||columnN FROM src_schema.big_table_view

这可以避免创建500000 StringBuilders和Strings. (假设没有涉及其他复杂格式). CHR(9)是制表符.

选项6:

同时,您还可以与DBA核实任何数据库系统问题,并使用Oracle support提出SR.

转载注明原文:java – 以极高的速度提取行 - 代码日志