- iOS/Objective-C 元类和类别
- objective-c - -1001 错误,当 NSURLSession 通过 httpproxy 和/etc/hosts
- java - 使用网络类获取 url 地址
- ios - 推送通知中不播放声音
我在 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 都是值得赞赏的。
更新:我整理并列出了以下讨论结果:
除了与 Oracle 数据库的连接之外,我无法访问 DBMS 服务器,并且服务器无法连接到任何外部资源。无法应用任何使用服务器或远程文件系统的转储和提取实用程序,也无法在服务器上安装和使用任何外部 java 或 PL/SQL 例程。仅连接以执行查询 - 仅此而已。
我使用了探查器并深入研究了 Oracle JDBC 驱动程序。我发现最昂贵的操作是读取数据,即 Socket.read()。所有字符串字段都表示为一个字符数组,对性能几乎没有影响。通常,我使用探查器检查了整个应用程序,而 Socket.read() 绝对是最昂贵的操作。提取字段、构建字符串、写入数据几乎不消耗任何东西。问题仅在于读取数据。
服务器端数据表示的任何优化都不会产生实际效果。连接字符串和转换时间戳对性能没有影响。
应用程序被重写为具有多个读取器线程,这些线程将就绪数据放入写入器队列。每个线程都有自己的连接,没有使用池,因为它们会减慢提取速度(我使用了 oracle 推荐的 UCP 池,它消耗了大约 10% 的执行时间,所以我放弃了)。结果集 fetchSize 也增加了,因为从默认值 (10) 切换到 50000 会带来高达 50% 的性能增长。
我测试了多线程版本如何与 4 个读取线程一起工作,发现增加读取器数量只会减慢提取速度。我尝试启动 2 个实例,其中每个实例都有两个读取器,并且都与单个实例同时工作,即双数据提取需要与单个实例相同的时间。不知道为什么会这样,但看起来 oracle 驱动程序有一些性能限制。具有 4 个独立连接的应用程序比具有 2 个连接的 2 个 App 实例运行得慢。(Profiler 用于确保驱动程序的 Socket.read() 仍然是主要问题,所有其他部分在多线程模式下工作正常)。
我尝试使用 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 个 StringBuilders
和 Strings
。 (假设不涉及其他复杂格式)。 CHR(9) 是制表符。
选项 6:
同时,您还可以与您的 DBA 检查任何数据库系统问题,并使用 Oracle support 提出 SR .
关于java - 以极高的速度获取行,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/25335813/
我是一名优秀的程序员,十分优秀!