Java读取PostgreSQL Blob时getBytes为何比InputStream.read更快
PostgreSQL Blob 读取:getBytes 远快于 InputStream.read 的原因
问题场景
我正在尝试从PostgreSQL数据库下载Blob数据,目前实现了两种下载方案:
- 方案1:使用
blob.getBytes(pos, BLOB_BUFFER_SIZE)方法下载Blob,实现代码如下:
private static void downloadBlob(HttpServletResponse response, java.sql.Blob blob) throws IOException, SQLException { try (ServletOutputStream outputStream = response.getOutputStream()) { outputStream.flush(); byte[] buffer; int bufferLength; long pos = 1; do { buffer = blob.getBytes(pos, BLOB_BUFFER_SIZE); bufferLength = buffer.length; pos += bufferLength; outputStream.write(buffer); outputStream.flush(); } while (bufferLength > 0); } catch (ClientAbortException e) { e.printStackTrace(); } }
- 方案2:使用
streamContent.read(buffer)方法从输入流读取数据完成下载,实现代码如下:
private static void writeStreamToResponse(HttpServletResponse response, InputStream streamContent) throws IOException { try (ServletOutputStream outputStream = response.getOutputStream()) { outputStream.flush(); byte[] buffer = new byte[BLOB_BUFFER_SIZE]; int bytesRead; do { bytesRead = streamContent.read(buffer); outputStream.write(buffer, 0, bytesRead); outputStream.flush(); } while (bytesRead != -1); } catch (ClientAbortException error) { LOGGER.error(error.getMessage()); } finally { streamContent.close(); } }
核心疑问
为什么调用getBytes()方法直接读取字节数组的执行效率,远高于调用inputStream.read(buffer)从输入流读取数据的效率?实际测试中streamContent.read(buffer)方法的读取耗时非常长。
原因说明
两种方法的性能差异本质是PostgreSQL JDBC驱动对两条读取路径的底层实现逻辑完全不同:
Blob.getBytes()的实现路径
调用该方法时,驱动默认会在第一次读取时就把对应Blob字段的全部数据一次性从服务端拉取到客户端JVM内存中做本地缓存,后续所有getBytes()调用都是直接从本地内存的字节数组中按偏移量、长度做内存拷贝,没有额外的网络交互、协议解析开销,内存拷贝的速度本身极快,因此整体耗时非常低。Blob.getBinaryStream()返回流的默认实现路径
这个输入流默认走PostgreSQL的大对象流式读取协议,设计目标是支持读取远超JVM内存上限的超大Blob,不会在初始化时把整个Blob加载到本地内存。默认配置下,每次调用read()方法时,驱动只会向服务端请求很小一块数据(多数驱动版本默认单块大小为2KB~4KB),不管你传入的buffer数组开多大,单次read最多只会返回默认块大小的数据。
这种模式下,读取整个Blob的网络往返次数 = Blob总大小 / 单次拉取块大小,大文件场景下网络往返次数会达到非常高的量级,再加上每一次请求的协议帧解析、TCP传输开销,整体耗时会远高于全量拉到内存再拷贝的getBytes()方案。
额外放大性能差距的代码问题
两份代码里都存在不必要的频繁flush()操作:每读取一块数据就调用outputStream.flush()会强制Servlet输出流把当前缓冲区的小包立刻推给客户端,跳过容器本身的输出缓冲优化,额外增加大量TCP小包发送开销。这个问题在本身读取速度慢的流方案里会被进一步放大,让耗时看起来更夸张。
优化方案
- 如果业务场景中Blob大小不会超过服务端JVM内存上限,直接用
getBytes()是最简单高效的选择,没有兼容性问题。 - 如果需要处理GB级别的超大Blob、无法全量加载到内存,可以在JDBC连接参数或者创建大对象流时手动设置
largeObjectBufferSize参数,把单次拉取的块大小调整到和你的BLOB_BUFFER_SIZE一致(推荐设为128KB~256KB),能把网络往返次数降低几个数量级,流读取的速度会和getBytes()接近。 - 移除循环内的
outputStream.flush()调用,等整个Blob全部写入输出流后再执行一次flush即可,Servlet容器本身会管理输出缓冲区的发送策略,减少不必要的flush能直接提升下载吞吐量。 - 如果使用的PostgreSQL JDBC驱动版本低于42.2.x,建议升级到新版本,旧版本大对象流的默认块大小仅为2KB,性能问题会更明显。
内容的提问来源于stack exchange,提问作者user19296905
相关产品推荐
相关产品推荐

