java - 以极高的速度获取行

标签 java sql oracle jdbc

我在 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 都是值得赞赏的。

更新: 我整理并列出了以下讨论结果:

  1. 除了与 Oracle 数据库的连接之外,我无法访问 DBMS 服务器,并且服务器无法连接到任何外部资源。无法应用任何使用服务器或远程文件系统的转储和提取实用程序,也无法在服务器上安装和使用任何外部 java 或 PL/SQL 例程。仅连接以执行查询 - 仅此而已。

  2. 我使用了探查器并深入研究了 Oracle JDBC 驱动程序。我发现最昂贵的操作是读取数据,即 Socket.read()。所有字符串字段都表示为一个字符数组,对性能几乎没有影响。通常,我使用探查器检查了整个应用程序,而 Socket.read() 绝对是最昂贵的操作。提取字段、构建字符串、写入数据几乎不消耗任何东西。问题仅在于读取数据。

  3. 服务器端数据表示的任何优化都不会产生实际效果。连接字符串和转换时间戳对性能没有影响。

  4. 应用程序被重写为具有多个读取器线程,这些线程将就绪数据放入写入器队列。每个线程都有自己的连接,没有使用池,因为它们会减慢提取速度(我使用了 oracle 推荐的 UCP 池,它消耗了大约 10% 的执行时间,所以我放弃了)。结果集 fetchSize 也增加了,因为从默认值 (10) 切换到 50000 会带来高达 50% 的性能增长。

  5. 我测试了多线程版本如何与 4 个读取线程一起工作,发现增加读取器数量只会减慢提取速度。 我尝试启动 2 个实例,其中每个实例都有两个读取器,并且都与单个实例同时工作,即双数据提取需要与单个实例相同的时间。不知道为什么会这样,但看起来 oracle 驱动程序有一些性能限制。具有 4 个独立连接的应用程序比具有 2 个连接的 2 个 App 实例运行得慢。 (Profiler 用于确保驱动程序的 Socket.read() 仍然是主要问题,所有其他部分在多线程模式下工作正常)。

  6. 我尝试使用 SAS 获取所有数据,它执行相同提取的速度比 JDBC 快 2 倍,两者都使用到 Oracle 的单一连接并且不能使用任何转储操作。 Oracle 确保 JDBC 瘦驱动程序与 native 驱动程序一样快。

也许 Oracle 有其他方法可以通过 ODBC 或其他方式对远程主机执行快速提取?

最佳答案

假设您已经检查了基本的网络内容,如接口(interface)、防火墙、代理,以及数据库服务器的硬件元素。

选项 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:获取大小

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

并且,对于 500,000 的提取大小,您的 -Xms 和 -Xmx 是多少。另外,在分析器中,最大堆大小是多少?

选项 3:DB

  • 检查src_schema.big_table_view

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

选项 4:线程

n <应用服务器上的内核数

您可以启动 n 编写器线程,每个线程都配置为处理某个桶,例如thread1 处理 0 到 10000,写入 n 不同的文件,一旦所有的文件都完成,post join,最好使用低级操作系统命令将文件合并在一起。

也就是说,所有这些都不应像现在这样是预定义代码。 'n' 并且桶应该在运行时计算。并且创建超过系统支持数量的线程只会搞砸。

选项 5:

代替

select * from src_schema.big_table_view

你可以使用

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

这避免了创建 500000 个 StringBuildersStrings。 (假设不涉及其他复杂格式)。 CHR(9) 是制表符。

选项 6:

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

关于java - 以极高的速度获取行,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/25335813/

相关文章:

java - 是否可以为 javafx textField/Area 中的不同行设置不同的颜色?

java - 从一个点到另一个点以相同的角度移动?

sql - Postgres 选择具有百分比匹配的行,并按百分比排序

c++ - 我可以从两个 oci 客户端维护一个 Oracle session 吗?

java - SQL 查询模式和相对性能

java - 如何使用 java 或 jdbcTemplate 从 Postgres 获取二维数组

java - 按顺序打印每个数组索引值的递归方法

sql - SQL查询根据条件从两个表中选择多列

sql - Mysql 自定义选择查询

sql - Oracle SQL : Select at least the first n rows, 继续直到列值与上一个不同