如何使用MATLAB和JDBC加速表检索?

时间:2014-04-23 12:23:10

标签: sql matlab postgresql jdbc resultset

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

由于我想检索大量数据,我正在寻找一种比现在更快的方式来提出请求。


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

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

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

然后我将columntypes存储在变量COLTYPE中(FLOAT为1,BOOLEAN为-1,其余为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数据库工具箱,所以我正在寻找没有它的解决方案。


据我所知,请求每个字段的数据非常无效。尽管如此,我还没有找到一种方法来同时获得更多数据 - 例如同一列的多行。有没有办法这样做?你有其他建议加快请求吗?

3 个答案:

答案 0 :(得分:7)

摘要

要加快速度,请使用数据库工具箱或自定义Java代码将循环推送,然后将列数据类型转换为Java层。 Matlab-to-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建议的数据库工具箱是一种方法,因为它们在Java中实现了它们的低级游标。 (可能正是出于这个原因。)但是如果你不能拥有数据库工具箱依赖关系,你可以编写自己的瘦Java层来实现,并从你的M代码中调用它。 (可能通过Matlab类与您的自定义Java代码相结合并知道如何与它进行交互。)使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驱动程序旋钮

确保您的代码将特定于DBMS的JDBC连接参数公开给您的M代码层,并使用它们。阅读您的特定DBMS的doco并适当地调整它们。例如,Oracle的JDBC驱动程序默认将默认的提取缓冲区大小(JDBC驱动程序中的一个,而不是你正在构建的那个)设置为大约10行,这对于典型的数据分析来说太小了设定尺寸。 (每次缓冲区填充时,都会导致网络往返数据库。)只需将其设置为1,000或10,000行就像打开" Go Fast"已经发货的开关设置为" off"。使用样本数据集对您的速度进行基准测试,并绘制结果图表以选择适当的设置。

优化列数据类型传输

除了为您提供块提取功能外,编写自定义Java代码还可以为特定列类型进行优化类型转换。在您处理了每行和每单元Java调用开销之后,您的瓶颈可能是在日期解析并将字符串从Java传递回Matlab。通过将SQL日期类型转换为Matlab datenum(因为Java加倍,带有列类型指示符),将日期解析向下推送到Java,因为它们被缓冲,可能使用缓存来避免重新计算重复日期在同一组中。 (注意TimeZone个问题。考虑Joda-Time。)将Java上的任何BigDecimal转换为double。并且瓶子是一个很大的瓶颈 - 一个炭柱可以淹没几个浮子柱的成本。如果可以的话,将窄CHAR列作为二维字符而不是像单元格返回(通过返回一个大的Java char[]然后使用reshape()),如果需要,在Matlab端转换为cellstr。 (返回Java String[]效率降低cellstr。)您可以通过将它们作为"符号"来优化对低基数字符列的检索。 - 在Java端,建立一个唯一字符串值的列表并将它们映射到数字代码,并将字符串作为数字代码的原始数组以及该数字地图返回 - >串;在Matlab端将不同的字符串转换为cellstr,然后使用索引将其扩展为完整数组。这样会更快并且节省大量内存,因为写时复制优化将为重复的字符串值重用相同的原始字符数据。或者,如果合适,将它们转换为categoricalordinal个对象而不是单元格。如果您使用大量字符数据并且具有大型结果集,则此符号优化可能是获胜,因为这样您的字符串列以大约原始数字速度传输,这实质上更快,并且它减少了celltr& #39;典型的内存碎片。 (数据库工具箱现在也可能支持这些东西了。几年后我还没有真正使用它。)

之后,根据您的DBMS,您可以通过将DBMS支持的所有数字列类型变量的映射包含在Matlab中的相应数值类型中,并尝试在模式中使用它们或进行转换,从而缩短速度。在您的SQL查询中。例如,通过像这样的db / Matlab堆栈,Oracle的BINARY_DOUBLE可以比正常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代码类是一种很好的方法。所以它看起来像这样。

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

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

定期测试和测试您的代码(在M代码和Java级别)以确保您的瓶颈位于您认为的位置,并查看是否存在需要根据您的数据集大小调整的参数,无论是行数还是列数和类型。我还想在Matlab和Java层构建一些检测和日志记录,以便您可以轻松地进行性能测量(例如,总结一下解析不同列类型所花费的时间,Java层中的多少以及多少Matlab层,以及服务器响应的等待时间(由于流水线操作可能不多,但你永远不知道))。如果您的DBMS公开其内部工具,也可以将其拉出来,这样您就可以看到您在服务器端花费的时间。

答案 1 :(得分:6)

您可以按照http://undocumentedmatlab.com/blog/speeding-up-matlab-jdbc-sql-queries中提出的解决方案进行操作。但是,在从PostgreSQL导入/导出相当大量的数据的情况下,使用JDBC可能效率不高。这可以通过本地Matlab格式(Java对象到Matlab,反之亦然)的重要数据转换开销来解释,例如,您正在处理的时间戳。但是JDBC本身对于无法解决的基本上大的数据集具有某些限制。在下面的图片中很好地说明了这一点(图片是用于数据插入而不是数据检索的事实,因为无论您在哪个方向传递数据,都不会因为开销而改变任何内容):

The case of scalar numeric data The case of arrays

此处,来自Matlab数据库工具箱的fastinsertdatainsert的性能(它们在任何情况下都通过JDBC与PostgreSQL一起使用,因此它们是能够显示JDBC本身短缺的相关代表)与batchParamExec之一 来自另一个名为PgMex的库(详见https://pgmex.alliedtesting.com/#batchparamexec)。第一张图是针对标量数字数据的情况,第二张图是针对数组的。每个图的端点对应 通过相应的方法传递到数据库的某个最大数据量没有任何错误。 数据量大于该最大值(特定于每个方法)会导致“Java堆内存不足”问题 (每个实验的Java堆大小在每个图的顶部指定)。 有关实验的更多详细信息,请参阅以下内容 "Performance comparison of PostgreSQL connectors in Matlab" article

解释是PgMex根本不使用JDBC,而是基于libpq(官方C应用程序程序员与PostgreSQL的接口)。这个库是用纯C编写的,它为小型和数据量大的数据库请求提供了显着的性能提升,因此即使对于数组也只有最小的开销 - 这要归功于Matlab和PostgreSQL之间没有任何文本解析的100%二进制数据传输。 同时,所有这些都是以Matlab友好和本地方式(以矩阵,多维数组,结构和任意其他Matlab格式的形式)完成的,因此,不执行Java对象到Matlab格式的转换。特别是,PgMex可以获取/放置PostgreSQL时间戳作为Matlab序列日期编号(有关详细信息,请参阅Matlab内置函数datenum的帮助),如下例所示。

关于数据检索的情况,初步实验表明,对于标量数值数据的最简单情况,PgMex比约3.5快,而不是通过JDBC。

上面的例子可以使用PgMex重新编写如下(我们假设在<>符号标记的所有参数的值正确填充之后,字符串变量TABLENAME确定了已存在的表的名称。相应的数据库,该表包含ts类型的字段timestamp以及float8boolvarchar数据类型的三个字段,以及最后,变量startTimeendTime是确定要检索的所需时间跨度的序列日期数字:

% Create the database connection
dbConn = com.allied.pgmex.pgmexec('connect',[...
    'host=<yourhost> dbname=<yourdb> port=<yourport> '...
    'user=<your_postgres_username> password=<your_postgres_password>']);

QUERYSTRING = ['SELECT * FROM ' TABLENAME ...
    ' WHERE ts BETWEEN $1 AND $2'];
pgParam = com.allied.pgmex.pgmexec('paramCreate',dbConn);
com.allied.pgmex.pgmexec('putf',pgParam,'%timestamp %timestamp',startTime,endTime);
pgResult = com.allied.pgmex.pgmexec('paramExec',dbConn,pgParam,QUERYSTRING);
[STs,SFloat,SBoolean,SString]=com.allied.pgmex.pgmexec('getf',pgResult,...
    '#timestamp %float8 %bool %varchar','ts',1,2,3);

此处最后一个命令的所有输出都是包含字段valueVecisNullVecisValueNullVec的结构。 valueVec包含相应表字段的值,而isNullVecisValueNullVec是NULL的指示符。 所有这些都是以Matlab友好和本地方式完成的,因此您无需进行任何转换。也就是说, STs.valueVec是带有Matlab序列日期编号的双列向量nTuples x 1SFloat.valueVec是Matlab double类型的列向量nTuples x 1SBoolean.valueVec是逻辑列向量 nTuples x 1 SString.valueVec是一个列单元格数组nTuples x 1,每个单元格都包含一个字符串 相应元组的相应表字段的值。 为一次调用命令检索所有元组的字段值。

编辑:PgMex的最新版本可以免费获得学术许可。

答案 2 :(得分:0)

在我看来,为了加快对表的查询,你必须删除,如果在JDBC ResulSetMetadata中为那提供有关列的数据类型和相同名称的信息,所以你保存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,任何事都让我知道!