Fetch Size 与 JDBC 内存管理

接触到 JDBC 的 Fetch Size 这个属性缘起一个性能问题,项目中需要将一个有千万级数据量的表中的记录导出到文件中去。按照正常的路数,先初始化连接;接着写好 SQL 语句,比如SELECT * FROM DIM_USERS;然后启动查询,拿到 ResultSet,最后遍历 ResultSet 将每行记录输出到文件中去。可在接下来的测试中,发现性能并不理想,在表中数据量小的时候,执行速度尚可接受,可是在进行大数据量压力测试的时候,发现代码往往要执行40分钟以上,这在实际生产环境上是万万不可接受的。

通过定位,发现性能瓶颈出现在从数据库中读取数据的时候,大概消耗了90%以上的时间。也就是说如果什么事情都不干,单纯对一个千万级数据量的 ResultSet 进行一次遍历就需要耗时35分钟以上。这样一来,问题就变得让人有点费解了,因为在同一套环境上,服务器向数据库写入数据的速率可以达到3万+/秒,为何查询变得如此低效?正在我百思不得其解的时候,一个大神走过来拍怕我的肩膀说:“小伙子,试试把 Fetch Size 调整一下。”

Fetch Size

在 JDBC 中 Fetch Size 是 Statement 上的一个属性,先看下Oracle 的帮助文档对它是怎么定义的:

By default, when Oracle JDBC executes a query, it receives the result set 10 rows at a time from the database cursor. This is the default Oracle row-prefetch value. You can change the number of rows retrieved with each trip to the database cursor by changing the row-prefetch value

简单的说,这个属性控制了 JDBC 每次读取数据的行数,由于 JDBC 每次都要通过网络去读取数据,如果这个值配置得太小,那么就意味着在遍历 ResultSet 的时候 JDBC 需要频繁的通过网络读取数据,这就导致了读取数据时性能低下。那接下来的问题就简单了,就是将这个属性调大。可是调整到多少合适呢?1K、2K?还是1W、2W?要知道 JDBC 每次读取的数据是会缓存在内存中的,如果这个属性设置大了,就会使程序出现 OOM。

JDBC Memory

接下来就得聊聊 JDBC 的内存管理了(这里特指 Oracle JDBC,别的厂商也许实现机制不是这样的)。JDBC 解析 SQL 语句后,为每个 Statement(包括 PreparedStatement 和 CallableStatement)分配了两个 Buffer 来缓存数据,byte[]char[]。字符类型的数据(CHAR, VARCHAR2, NCHAR, etc. )缓存在char[]中,其他类型的数据缓存在byte[]中。在 SQL 语句解析后,语句所查询的列的数据类型就已经确定了,JDBC 会根据这些信息和 Fetch Size 一起计算出缓存的大小,并分配内存。所以如果不需要查询某张表的所以列时,使用SELECT * FROM XXX是一种浪费内存的行为,特别是表的列数多且数据量大的时候,很容易造成 OOM。

数据类型与内存占用

前面说了,JDBC 会根据查询语句中列的数据类型来计算缓存的大小那么每种数据类型大致占多少空间呢?请看下表。

数据类型

大小(byte)

备注

VARCHAR2

2

每个字符占用2byte

BFILE

4K

BLOB

4K

CLOB

4K

Other

22

其他类型占用空间比较小,可以大致估算为22byte

让我们来举个栗子:

CREATE TABLE TAB (ID NUMBER(10), NAME VARCHAR2(40), DOB DATE)ResultSet r = stmt.executeQuery(“SELECT * FROM TAB”);

当 JDBC 解析查询语句时,数据库会告知 JDBC 结果会包含三列,NUMBER(10)、VARCHAR2(40) 和 DATE,第一列大概需要22 bytes,第二列包含了40个字符,所以需要2 * 40bytes,第三列也是大概需要22 bytes。因此,本次查询每条记录大致需要22 + (40 * 2) + 22 = 124bytes,如果 Fetch Size设置为10,那么缓存就需要分配1240 bytes 的空间。

如何正确设置Fetch Size

上面说了那么多无非就是想说明一个问题,就是 Fetch Size 的大小是要根据实际情况来设置,设置小了性能不好,设置大了内存会有问题。总之一个原则就是,在保证内存够用的情况下,尽量把 Fetch Size 设置得大一点。如果你拿不准设置多少,可以先试下下面的方式:

4 * 1024 * 1024 / sum(所读取的列的数据长度)

参考文档:

本文参与腾讯云自媒体分享计划,欢迎正在阅读的你也加入,一起分享。

发表于

我来说两句

0 条评论
登录 后参与评论

相关文章

来自专栏大内老A

创建代码生成器可以很简单:如何通过T4模板生成代码?[上篇]

在《基于T4的代码生成方式》中,我对T4模板的组成结构、语法,以及T4引擎的工作原理进行了大体的介绍,并且编写了一个T4模板实现了如何将一个XML转变成C#代码...

6158
来自专栏乐沙弥的世界

Oracle 外部表

外部表只能在Oracle 9i 之后来使用。简单地说,外部表,是指不存在于数据库中的表。通过向Oracle提供描述外部表的元数据,我们

822
来自专栏乐沙弥的世界

共享池的调整与优化(Shared pool Tuning)

--=======================================

922
来自专栏乐沙弥的世界

Oracle 表缓存(caching table)的使用

在通常的情况下,应用程序访问在cache中的数据块将按照LRU算法来进行处理。然而对于小表的访问,当使用全表扫描时,则该表

1164
来自专栏互联网杂技

SQL注入攻防入门详解

毕业开始从事winfrm到今年转到 web ,在码农届已经足足混了快接近3年了,但是对安全方面的知识依旧薄弱,事实上是没机会接触相关开发……必须的各种借口。这几...

48910
来自专栏学习有记

SQL Server 索引内部结构:SQL Server 索引进阶 Level 10

1184
来自专栏PHP在线

MySQL的语句执行顺序

MySQL的语句一共分为11步,如下图所标注的那样,最先执行的总是 FROM操作,最后执行的是LIMIT操作。其中每一个操作都会产生一张虚拟的表,这个虚拟的表作...

65210
来自专栏文渊之博

优化SqlServer--数据压缩

 数据压缩是对存储和性能优势的加强。减少数据库占用的磁盘空间量将减少整体数据文件存储空间,在一下几个方面增加吞吐量:      1.更好的I/O利用率,每个页面...

1927
来自专栏杨建荣的学习笔记

简单分析shared pool(二) (r3笔记48天)

对于shared pool的学习,发现越尝试去了解,发现自己对它越不了解。里面的东西很杂。 自己想用几个问题来作为引子来说明更加会有条理一些。 shared ...

2934
来自专栏程序员的SOD蜜

TOP语句放到表值函数外,效率异常低下的原因分析

SQLSERVER的表值函数是SQLSERVER 2005以来的新特性,由于它使用比较方便,就像一个单独的表一样,在我们的系统中大量使用。有一个获取客户数据的S...

2079

扫码关注云+社区