gpt4 book ai didi

java - 以极高的速度获取行

转载 作者:塔克拉玛干 更新时间:2023-11-03 05:17:27 35 4
gpt4 key购买 nike

我在 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/

35 4 0
Copyright 2021 - 2024 cfsdn All Rights Reserved 蜀ICP备2022000587号
广告合作:1813099741@qq.com 6ren.com