【问题标题】:How to speed up table-retrieval with MATLAB and JDBC?如何使用 MATLAB 和 JDBC 加速表检索?
【发布时间】:2014-06-08 06:42:01
【问题描述】:

我正在使用 MATLAB 调用的 JDBC 访问 PostGreSQL 8.4 数据库。 我感兴趣的表基本上由不同数据类型的各种列组成。它们是通过时间戳选择的。

由于我想检索大量数据,因此我正在寻找一种比现在更快地发出请求的方法。


我目前正在做的事情如下: 首先,我建立到数据库的连接并将其命名为DBConn。下一步是准备一个 Select-Statement 并执行它:

QUERYSTRING = ['SELECT * FROM ' TABLENAME '...
WHERE ts BETWEEN ''' TIMESTART ''' AND ''' TIMEEND ''''];

QUERY = DBConn.prepareStatement(QUERYSTRING);
RESULTSET = QUERY.executeQuery();

然后我将列类型存储在变量 COLTYPE 中(1 表示 FLOAT,-1 表示 BOOLEAN,0 表示其余部分 - 几乎所有列都包含 FLOAT)。下一步是处理每一行,逐列,并通过相应的方法检索数据。 FNAMES 包含表的字段名。

m=0; % Variable containing rownumber

while RESULTSET.next()
  m = m+1;

  for n = 1:length(FNAMES)

    if COLTYPE(n)==1 % Columntype is a FLOAT
      DATA{1}.(FNAMES{n})(m,1) = RESULTSET.getDouble(n);
    elseif COLTYPE(n)==-1 % Columntype is a BOOLEAN
      DATA{1}.(FNAMES{n})(m,1) = RESULTSET.getBoolean(n);
    else
      DATA{1}.(FNAMES{n}){m,1} = char(RESULTSET.getString(n));
    end

  end

end

当我完成我的请求后,我关闭语句和连接。

我没有 MATLAB 数据库工具箱,所以我正在寻找没有它的解决方案。


我了解请求每个字段的数据是非常无效的。尽管如此,我还是没能找到一次获取更多数据的方法——例如同一列的多行。有什么办法吗?您还有其他加快请求速度的建议吗?

【问题讨论】:

  • 由于我还是一个新用户,我很高兴有任何关于如何改进我的问题并使其更容易回答的评论。任何其他限制对该问题的兴趣和回答的原因也是受欢迎的。
  • 检查exec()fetch()。我使用 JDBC 在 6 秒内从 MySQL 数据库获得 17k 行。还可以考虑使用'DataReturnFormat''Dataset''Table',参见setdbprefs()。如果这解决了,我会把它作为一个正确的答案。
  • @OlegKomarov 这些都是数据库工具箱的一部分。我对exec()fetch 使用开放式替代方案,它们完全符合我在问题中的描述。我不能依赖数据库工具箱。
  • 您应该在问题中包含此信息。另外,你的意思是你不能依赖 db 工具箱,因为你没有它?
  • @OlegKomarov 完全正确。我没有,所以我必须使用不同的方法。对不起,我没有直接提到。

标签: sql matlab postgresql jdbc resultset


【解决方案1】:

我突然想到,为了加快对表的查询,您必须删除 if,因为那里的 JDBC ResulSetMetadata 为您提供有关列的数据类型和相同名称的信息,所以您保存if 的时间,else if

  ResultSetMetaData RsmD = (ResultSetMetaData) rs.getMetaData (); 

        int cols=rsMd.getColumnCount();
        
  while (rs.next) 
   for i=1 to cols
     row[i]=rs.getString(i);

我的例子是伪代码,因为我不是 matlab 程序员。

我希望你觉得它对 JDBC 有用,有什么告诉我!

【讨论】:

  • 嗨@santiago92,感谢您的回答。我不认为这会有所作为,因为更好地使用不同的方法将使解析数据库中的数据更有效。即使使用ifelseif,这可能会更准确、更快。
【解决方案2】:

总结

为加快此过程,请使用数据库工具箱或自定义 Java 代码将循环和列数据类型转换下推到 Java 层。 Matlab 到 Java 的方法调用开销可能是要你命的原因,而且没有办法用普通的 JDBC 进行块提取(一次调用中的多行)。确保您正在使用的 JDBC 驱动程序上的旋钮设置正确。然后优化昂贵的列数据类型(如字符串和日期)的传输。

(注意:我没有在 Postgres 上做过这个,但在其他 DBMS 上做过,这也适用于 Postgres,因为其中大部分是关于它上面的 JDBC 和 Matlab 层。)

详情

将循环下推到 Java 以获取块

加快速度的最直接方法是将行和列上的循环向下推送到 Java 层,并让它将数据块(例如,一次 100 或 1000 行)返回到 Matlab 层。从 Matlab 调用 Java 方法会产生大量的每次调用开销,并且会导致在 M 代码中循环 JDBC 调用(请参阅Is MATLAB OOP slow or am I doing something wrong? - 完全披露:这是我的答案)。如果您像这样从 M 代码调用 JDBC,那么您会在每一行的每一列上产生这种开销,而这可能是您现在执行时间的大部分时间。

JDBC API 本身不支持像 ODBC 那样的“块游标”,因此您需要将该循环向下传递到 Java 层。使用 Oleg 建议的 Database Toolbox 是一种方法,因为它们在 Java 中实现了较低级别的游标。 (可能正是因为这个原因。)但是如果您不能拥有数据库工具箱依赖项,您可以编写自己的薄 Java 层来实现这一点,并从您的 M 代码中调用它。 (可能通过与您的自定义 Java 代码耦合并知道如何与之交互的 Matlab 类。)使 Java 代码和 Matlab 代码共享一个块大小,在 Java 端缓冲整个块,使用原始数组而不是尽可能使用列缓冲区的对象数组,并让您的 M 代码批量获取结果集,将这些块缓冲在 primitive 列数组的单元数组中,然后将它们连接在一起。

Matlab 层的伪代码:

colBufs = repmat( {{}}, [1 nCols] );
while (cursor.hasMore())
    cursor.fetchBlock();
    for iCol = 1:nCols
        colBufs{iCol}{end+1} = cursor.getBlock(iCol); % should come back as primitive
    end
end
for iCol = 1:nCols
    colResults{iCol} = cat(2, colBufs{iCol}{:});
end

旋转 JDBC DBMS 驱动程序旋钮

确保您的代码向您的 M 代码层公开特定于 DBMS 的 JDBC 连接参数,并使用它们。阅读特定 DBMS 的文档并适当地摆弄它们。例如,Oracle 的 JDBC 驱动程序默认将默认提取缓冲区大小(在他们的 JDBC 驱动程序中,而不是您正在构建的那个)设置为大约 10 行,这对于典型的数据分析集大小来说太小了。 (每次缓冲区填满时,都会导致到数据库的网络往返。)简单地将其设置为 1,000 或 10,000 行就像打开已设置为“off”的“Go Fast”开关。使用示例数据集对您的速度进行基准测试并绘制结果图表以选择适当的设置。

优化列数据类型传输

除了为您提供块提取功能外,编写自定义 Java 代码还可以为特定列类型进行优化类型转换。在处理完每行和每单元的 Java 调用开销后,您的瓶颈可能在于日期解析和将字符串从 Java 传递回 Matlab。通过将 SQL 日期类型转换为 Matlab datenums(作为 Java 双倍,带有列类型指示符)将日期解析推入 Java,因为它们正在被缓冲,可能使用缓存来避免重新计算重复日期同一套。 (注意TimeZone 问题。考虑Joda-Time。)在Java 端将任何BigDecimals 转换为double。而且 cellstrs 是一个很大的瓶颈——单个 char 列可能会淹没几个 float 列的成本。如果可以的话,将窄 CHAR 列返回为二维字符而不是 cellstrs(通过返回一个大的 Java char[] 然后使用 reshape()),如有必要,在 Matlab 端转换为 cellstr。 (将 Java String[]converts 返回到 cellstr 效率较低。)您可以通过将低基数字符列作为“符号”传回来优化低基数字符列的检索 - 在 Java 端,构建唯一字符串的列表值并将它们映射到数字代码,并将字符串作为数字代码的原始数组以及数字 -> 字符串的映射返回;在 Matlab 端将不同的字符串转换为 cellstr,然后使用索引将其扩展为完整数组。这会更快,也可以节省大量内存,因为写时复制优化将为重复的字符串值重用相同的原始 char 数据。或者,如果合适,将它们转换为categoricalordinal 对象而不是cellstrs。如果您使用大量字符数据并拥有大型结果集,则此符号优化可能是一个胜利,因为您的字符串列以大约原始数字速度传输,这要快得多,并且它减少了 cellstr 的典型的内存碎片。 (Database Toolbox 现在可能也支持其中的一些东西。我已经有几年没有真正使用它了。)

之后,根据您的 DBMS,您可以通过将 DBMS 支持的所有数字列类型变体映射到 Matlab 中的适当数字类型,并尝试在架构中使用它们或进行转换,从而加快速度在您的 SQL 查询中。例如,Oracle 的 BINARY_DOUBLE 在像这样通过 db/Matlab 堆栈的完整行程中可能比其正常的 NUMERIC 快一点。 YMMV。

您可以考虑通过使用更便宜的数字标识符替换字符串和日期列来优化此用例的架构,可能作为分隔查找表的外键以将它们解析为原始字符串和日期。具有足够架构知识的客户端可以缓存查找。

如果您想发疯,您可以在 Java 级别使用多线程,使其异步预取并在单独的 Java 工作线程上解析下一个结果块,如果您可以并行处理每列的日期和字符串具有较大的光标块大小,而您正在对前一个块进行 M 代码级处理。但这确实增加了难度,理想情况下是一个小的性能提升,因为您已经将昂贵的数据处理推到了 Java 层。把这个留到最后。并检查JDBC驱动doco;它可能已经有效地为您执行此操作。

杂项

如果您不愿意编写自定义 Java 代码,您仍然可以通过将 Java 方法调用的语法从 obj.method(...) 更改为 method(obj, ...) 来获得一些加速。例如。 getDouble(RESULTSET, n)。这只是一个奇怪的 Matlab OOP 怪癖。但这不会有多大的收获,因为您仍然需要为每次调用的 Java/Matlab 数据转换付费。

另外,请考虑更改您的代码,以便您可以在 SQL 查询中使用 ? 占位符和绑定参数,而不是将字符串插入为 SQL 文字。如果你在做一个自定义的 Java 层,定义你自己的 @connection 和 @preparedstatement M-code 类是一个不错的方法。所以它看起来像这样。

QUERYSTRING = ['SELECT * FROM ' TABLENAME ' WHERE ts BETWEEN ? AND ?'];
query = conn.prepare(QUERYSTRING);
rslt = query.exec(startTime, endTime);

这将为您提供更好的类型安全性和更可读的代码,并且还可以减少查询解析的服务器端开销。在只有几个客户端的情况下,这不会给您带来太多的加速,但它会让编码变得更容易。

定期分析和测试您的代码(在 M 代码和 Java 级别)以确保您的瓶颈在您认为的位置,并查看是否有需要根据您的数据集大小调整的参数,无论是在行数还是列数和类型方面。我还喜欢在 Matlab 和 Java 层构建一些仪器和日志记录,这样您就可以轻松获得性能测量结果(例如,让它总结解析不同列类型所花费的时间、Java 层中的时间以及Matlab 层,以及等待服务器响应的时间(可能由于流水线而等待的时间不多,但你永远不知道)。如果您的 DBMS 公开了其内部工具,也可以将其引入,这样您就可以看到您在哪里花费了服务器端时间。

【讨论】:

  • 哇。感谢您的回答,对我有很多了解和学习。我将在下周完成它并尽可能多地尝试,尽管我可能不得不为此减少我的 Java 知识。非常感谢!
  • 我不明白这部分:在原始列数组的元胞数组中缓冲这些块详细信息的第一段)。 原始列数组是什么意思?什么是非原始的?
  • "Primitives" = Matlab doublesingle[u]int[N] 类型,或者(如果您想扩展定义)表示字符串的二维 char 数组。 Nonprimitives = structs、cellstrs、cells、Matlab 对象、Java 对象、嵌套单元格或其他任何东西。基本上,原语是内置的 Matlab 数据类型,可以表示为内存中的连续字节数组,包装在单个 MxArray 中,不涉及其他间接层(指针或包装器)。
  • 不客气!并且不要忘记您的 Java 背景;其中一半是了解 Java 与 Matlab 相比可以高效地做什么,以及 Java 类型如何映射到 Matlab 类型。
  • +1 用于检索块中的数据与单个调用。这会显着提高速度。
猜你喜欢
  • 2021-03-28
  • 1970-01-01
  • 2013-03-07
  • 2020-02-10
  • 2014-01-26
  • 1970-01-01
  • 2011-08-02
  • 1970-01-01
  • 2011-11-24
相关资源
最近更新 更多