110 Tried to send an out-of-range integer as a 2-byte value: 40003

前言

呵呵 这个也是一个之前碰到的一个问题, 主要的问题是 发送给数据库的 sql 参数太多了, 呵呵 超过了 postgres 客户端, 服务器 交互的参数的数量的上限了 

之前记录了一个 todo, 最近的时候 看了一下 这部分的代码, 以及 涉及到的相关的协议 

另外 例子中会有一个对比, 就是通过 jpa 的 specification 交互的参数来传递这 4w 个参数是有问题的, 会抛出异常 

但是假设 我们是将 4w 个参数的查询封装成 sql 语句, 直接传递个服务器, 那么是可以正常执行的

可以参见 下面的 findBySpeficication 部分和 下面的 jdbcTemplate.queryForObject 的部分的差异 

 

 

测试用例

呵呵 repository 基于 JPARepository, 这里为了构造情况, 因此构造了一个 id not in (param1, param2, ... param40000) 

watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L3UwMTEwMzkzMzI=,size_16,color_FFFFFF,t_70

 

完整的堆栈信息大致如下 

Caused by: org.hibernate.exception.JDBCConnectionException: could not extract ResultSet
	at org.hibernate.exception.internal.SQLStateConversionDelegate.convert(SQLStateConversionDelegate.java:112)
	at org.hibernate.exception.internal.StandardSQLExceptionConverter.convert(StandardSQLExceptionConverter.java:42)
	at org.hibernate.engine.jdbc.spi.SqlExceptionHelper.convert(SqlExceptionHelper.java:113)
	at org.hibernate.engine.jdbc.spi.SqlExceptionHelper.convert(SqlExceptionHelper.java:99)
	at org.hibernate.engine.jdbc.internal.ResultSetReturnImpl.extract(ResultSetReturnImpl.java:67)
	at org.hibernate.loader.Loader.getResultSet(Loader.java:2341)
	at org.hibernate.loader.Loader.executeQueryStatement(Loader.java:2094)
	at org.hibernate.loader.Loader.executeQueryStatement(Loader.java:2056)
	at org.hibernate.loader.Loader.doQuery(Loader.java:953)
	at org.hibernate.loader.Loader.doQueryAndInitializeNonLazyCollections(Loader.java:350)
	at org.hibernate.loader.Loader.doList(Loader.java:2887)
	at org.hibernate.loader.Loader.doList(Loader.java:2869)
	at org.hibernate.loader.Loader.listIgnoreQueryCache(Loader.java:2701)
	at org.hibernate.loader.Loader.list(Loader.java:2696)
	at org.hibernate.loader.hql.QueryLoader.list(QueryLoader.java:506)
	at org.hibernate.hql.internal.ast.QueryTranslatorImpl.list(QueryTranslatorImpl.java:400)
	at org.hibernate.engine.query.spi.HQLQueryPlan.performList(HQLQueryPlan.java:219)
	at org.hibernate.internal.SessionImpl.list(SessionImpl.java:1415)
	at org.hibernate.query.internal.AbstractProducedQuery.doList(AbstractProducedQuery.java:1565)
	at org.hibernate.query.internal.AbstractProducedQuery.list(AbstractProducedQuery.java:1533)
	at org.hibernate.query.Query.getResultList(Query.java:165)
	at org.hibernate.query.criteria.internal.compile.CriteriaQueryTypeQueryAdapter.getResultList(CriteriaQueryTypeQueryAdapter.java:76)
	at com.hello.world1.repository.jpa.AbstractJpaRepository.readPage(AbstractJpaRepository.java:130)
	at com.hello.world1.repository.jpa.AbstractJpaRepository.findBySpecification(AbstractJpaRepository.java:97)
	at com.hello.world.repository.custom.impl.NotificationTemplateRepositoryImpl.search(NotificationTemplateRepositoryImpl.java:47)
	at com.hello.world.repository.custom.impl.NotificationTemplateRepositoryImpl$$FastClassBySpringCGLIB$$215758fa.invoke(<generated>)
	at org.springframework.cglib.proxy.MethodProxy.invoke(MethodProxy.java:218)
	at org.springframework.aop.framework.CglibAopProxy$CglibMethodInvocation.invokeJoinpoint(CglibAopProxy.java:771)
	at org.springframework.aop.framework.ReflectiveMethodInvocation.proceed(ReflectiveMethodInvocation.java:163)
	at org.springframework.aop.framework.CglibAopProxy$CglibMethodInvocation.proceed(CglibAopProxy.java:749)
	at org.springframework.dao.support.PersistenceExceptionTranslationInterceptor.invoke(PersistenceExceptionTranslationInterceptor.java:139)
	... 120 common frames omitted
Caused by: org.postgresql.util.PSQLException: An I/O error occurred while sending to the backend.
	at org.postgresql.core.v3.QueryExecutorImpl.execute(QueryExecutorImpl.java:337)
	at org.postgresql.jdbc.PgStatement.executeInternal(PgStatement.java:447)
	at org.postgresql.jdbc.PgStatement.execute(PgStatement.java:368)
	at org.postgresql.jdbc.PgPreparedStatement.executeWithFlags(PgPreparedStatement.java:158)
	at org.postgresql.jdbc.PgPreparedStatement.executeQuery(PgPreparedStatement.java:108)
	at com.zaxxer.hikari.pool.ProxyPreparedStatement.executeQuery(ProxyPreparedStatement.java:52)
	at com.zaxxer.hikari.pool.HikariProxyPreparedStatement.executeQuery(HikariProxyPreparedStatement.java)
	at org.hibernate.engine.jdbc.internal.ResultSetReturnImpl.extract(ResultSetReturnImpl.java:57)
	... 146 common frames omitted
Caused by: java.io.IOException: Tried to send an out-of-range integer as a 2-byte value: 40003
	at org.postgresql.core.PGStream.sendInteger2(PGStream.java:266)
	at org.postgresql.core.v3.QueryExecutorImpl.sendParse(QueryExecutorImpl.java:1500)
	at org.postgresql.core.v3.QueryExecutorImpl.sendOneQuery(QueryExecutorImpl.java:1823)
	at org.postgresql.core.v3.QueryExecutorImpl.sendQuery(QueryExecutorImpl.java:1386)
	at org.postgresql.core.v3.QueryExecutorImpl.execute(QueryExecutorImpl.java:302)
	... 153 common frames omitted

 

抛出异常的地方如下, 可以看到这里约束的 parameterCount 是在 2字节 

watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L3UwMTEwMzkzMzI=,size_16,color_FFFFFF,t_70

 

sendInteger2 的校验 

可以看出这里是如果 val 超过了 32767 的话, 就回抛出异常?, 但是 2byte 可以表示 65536 阿? 先暂时理解为这里参数的长度最大为 32767, 我们后面再看看 "参数长度为 int16 客户端这边的限制为 32767 ? "

watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L3UwMTEwMzkzMzI=,size_16,color_FFFFFF,t_70

 

我们查看一下原始的 传递的 sql, 也能更清晰的意识到这 4w 多个参数分别是什么 

select distinct bank0_.ID as id1_56_, 省略部分参数, from BANK bank0_ where (bank0_.TITLE_TEMPLATE not like $1) and (bank0_.CONTENT_TEMPLATE not like $2) and (bank0_.ID not in  ($3 , $4 , $5 , $6, 省略部分参数 , $39996 , $39997 , $39998 , $39999 , $40000 , $40001 , $40002)) order by bank0_.CREATED_ON desc limit $40003

 

 

客户端参数发送 

呵呵 这是我之前记录下来的一些东西, 核心的是关于 客户端给服务端传递的数据, 待会儿 我们可以 参照规范看一下  

QueryExecutorImpl. sendOneQuery, add at 2020.12.21
// sendOneQuery sends a single statement via the extended query protocol.
// Per the FE/BE docs this is essentially the same as how a simple query runs
// (except that it generates some extra acknowledgement messages, and we
// can send several queries before doing the Sync)
//
// Parse S_n from "query string with parameter placeholders"; skipped if already done previously
// or if oneshot
// Bind C_n from S_n plus parameters (or from unnamed statement for oneshot queries)
// Describe C_n; skipped if caller doesn't want metadata
// Execute C_n with maxRows limit; maxRows = 1 if caller doesn't want results
// (above repeats once per call to sendOneQuery)
// Sync (sent by caller)

1. sendParse
    pgStream.sendChar('P'); // Parse
    pgStream.sendInteger4(encodedSize);
    if (encodedStatementName != null) {
      pgStream.send(encodedStatementName);
    }
    pgStream.sendChar(0); // End of statement name
    pgStream.send(queryUtf8); // Query string
    pgStream.sendChar(0); // End of query string.
    pgStream.sendInteger2(params.getParameterCount()); // # of parameter types specified
    for (int i = 1; i <= params.getParameterCount(); ++i) {
      pgStream.sendInteger4(params.getTypeOID(i));
    }

2. sendDescribeStatement
    pgStream.sendChar('D'); // Describe
    pgStream.sendInteger4(encodedSize); // Message size
    pgStream.sendChar('S'); // Describe (Statement);
    if (encodedStatementName != null) {
      pgStream.send(encodedStatementName); // Statement name
    }
    pgStream.sendChar(0); // end message

3. sendBind
    pgStream.sendChar('B'); // Bind
    pgStream.sendInteger4((int) encodedSize); // Message size
    if (encodedPortalName != null) {
      pgStream.send(encodedPortalName); // Destination portal name.
    }
    pgStream.sendChar(0); // End of portal name.
    if (encodedStatementName != null) {
      pgStream.send(encodedStatementName); // Source statement name.
    }
    pgStream.sendChar(0); // End of statement name.

    pgStream.sendInteger2(params.getParameterCount()); // # of parameter format codes
    for (int i = 1; i <= params.getParameterCount(); ++i) {
      pgStream.sendInteger2(params.isBinary(i) ? 1 : 0); // Parameter format code
    }

    pgStream.sendInteger2(params.getParameterCount()); // # of parameter values

    // If an error occurs when reading a stream we have to
    // continue pumping out data to match the length we
    // said we would. Once we've done that we throw
    // this exception. Multiple exceptions can occur and
    // it really doesn't matter which one is reported back
    // to the caller.
    //
    PGBindException bindException = null;

    for (int i = 1; i <= params.getParameterCount(); ++i) {
      if (params.isNull(i)) {
        pgStream.sendInteger4(-1); // Magic size of -1 means NULL
      } else {
        pgStream.sendInteger4(params.getV3Length(i)); // Parameter size
        try {
          params.writeV3Value(i, pgStream); // Parameter value
        } catch (PGBindException be) {
          bindException = be;
        }
      }
    }

    pgStream.sendInteger2(numBinaryFields); // # of result format codes
    for (int i = 0; i < numBinaryFields; ++i) {
      pgStream.sendInteger2(fields[i].getFormat());
    }

4. sendExecute
    // Total size = 4 (size field) + 1 + N (source portal) + 4 (max rows)
    pgStream.sendChar('E'); // Execute
    pgStream.sendInteger4(4 + 1 + encodedSize + 4); // message size
    if (encodedPortalName != null) {
      pgStream.send(encodedPortalName); // portal name
    }
    pgStream.sendChar(0); // portal name terminator
    pgStream.sendInteger4(limit); // row limit

QueryExecutorImpl. sendQuery, 之后会发送一个 sync 的命令
QueryExecutorImpl. sendSync
    pgStream.sendChar('S'); // Sync
    pgStream.sendInteger4(4); // Length
    pgStream.flush();

 

 

客户端接收服务端的响应

QueryExecutorImpl. processResults : 从服务端获取数据, 并且封装 ResultSet
客户端这边 依次会收到服务器这边的 "Close Complete", "Parse Complete", ""
1. Close Complete : 消费了服务器返回回来的长度信息
2. Parse Complete : 消费了服务器返回回来的长度信息, 并且移除了 sendParse 放到 pendingParseQueue 里面的 query
3. Bind Complete : 消费了服务器返回回来的长度信息, 并且移除了 sendBind 放到 pendingBindQueue 里面的 portal, 并向 portalMap 注册
4. Row Description : 消费了服务器返回回来的长度信息, 并且移除了 sendParse 放到 pendingDescribePortalQueue 里面的 query, 并设置了 fields 的元数据
5. Data Transfer : 消费了服务器返回的数据的信息, 并记录在了 tuples 的容器里面
6. Command Status : 消费了服务器返回的状态信息, 并且移除了 sendExecute 放到 pendingExecuteQueue 里面的 executeRequest, 校验 fields 元数据 + 服务器返回的数据, 并且处理数据, 封装 ResultSet
7. Ready For Query : SYNC 的响应, 移除 SYNC 存放在 pendingExecuteQueue 里面的 executeRequest, 清理掉 pendingParseQueue, pendingBindQueue, pendingDescribePortalQueue, pendingExecuteQueue


Loader.doQuery 里面对于结果进行处理
这里的处理 就不细看了, 呵呵 服务器那边返回的 字节序列 byte[][] 直接 transform 一下, 就是对应的 一行的数据

 

 

章44. 前/后端协议

呵呵 这个应该才是 本文的重点, 参照的协议, 章44. 前/后端协议 

上面的客户端发送给服务端的数据如何组织, 服务端返回给客户端的数据怎么解析, 都是基于协议的 

另外本文的 参数数量只使用 2个字节 来表示长度 为什么会这么约束? 都可以在协议中找到原因  

我们这里着重需要关注的是 44.4. 消息格式 

备注一下一部分的内容, 截图 来自于文档 44.4. 消息格式 

 

 

扩展查询

我们上面从客户端代码中看到的查询, 在规范中称之为 44.2.3. 扩展查询

watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L3UwMTEwMzkzMzI=,size_16,color_FFFFFF,t_70

 

 

本文相关客户端发送的一些请求协议如下 

Parse 

呵呵 从这里可以看出为什么 Parse 阶段 参数数量只使用 2个字节 来表示长度, 看到这个 int16 了么 

watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L3UwMTEwMzkzMzI=,size_16,color_FFFFFF,t_70

 

Describe

watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L3UwMTEwMzkzMzI=,size_16,color_FFFFFF,t_70

 

Bind

watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L3UwMTEwMzkzMzI=,size_16,color_FFFFFF,t_70

 

Execute 

watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L3UwMTEwMzkzMzI=,size_16,color_FFFFFF,t_70

 

Sync

watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L3UwMTEwMzkzMzI=,size_16,color_FFFFFF,t_70

 

 

本文相关服务端发送的一些请求协议如下 

CloseComplete

watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L3UwMTEwMzkzMzI=,size_16,color_FFFFFF,t_70

 

 

ParseComplete

watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L3UwMTEwMzkzMzI=,size_16,color_FFFFFF,t_70

 

BindComplete

watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L3UwMTEwMzkzMzI=,size_16,color_FFFFFF,t_70

 

RowDescription 

watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L3UwMTEwMzkzMzI=,size_16,color_FFFFFF,t_70

 

DataTransfer 

watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L3UwMTEwMzkzMzI=,size_16,color_FFFFFF,t_70

 

CommandComplete

watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L3UwMTEwMzkzMzI=,size_16,color_FFFFFF,t_70

 

ReadyForQuery

watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L3UwMTEwMzkzMzI=,size_16,color_FFFFFF,t_70

 

 

参数长度为 int16 客户端这边的限制为 32767 ? 

呵呵 众所周知, 2个字节 无符号整数表示的话, 最多可以表示到 0xffff = 65535, 但是为什么这里的限定是 32767 呢? 

我的理解是, 属于客户端这边的一些限制吧?, 可能是处理这块的时候 没有想到什么太好的转换方式吧 

这里我们来尝试, 调整一下, 呵呵 

在 PGStream. sendInteger2 之前加上一段如下处理, 做一些有符号到无符号的数据的适配处理, 当然这里只是简单处理, 只是为了验证这里的主体, 可能没有太多的其他的各种情况的考虑 

if(val > Short.MAX_VALUE) {
    val = val - 65536;
}

 

然后调整一下我们这里的测试用例, 呵呵 4w 个已有的参数, 再加上数据库中已经存在的大部分的 实体的 id, 去掉了三个是用于测试效果, 呵呵 

watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L3UwMTEwMzkzMzI=,size_16,color_FFFFFF,t_70

 

然后我们可以查看一下返回的数据, 呵呵 似乎是突破了目前客户端已有的 32767 的限制, 当然作用也不是太大, 只是吧这里的数据交互 更加逻辑严谨化了一些而已 

watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L3UwMTEwMzkzMzI=,size_16,color_FFFFFF,t_70

 

 

jdbcTemplate. queryForObject 不是走的上面的 P+D+B+E+S ?

最开始我碰到这个问题, 想要看下 相关代码的时候, 我当时的复现方式是 想使用 jdbcTemplate 封装 sql 构造大量的参数来试试, 谁知道 灭有成功 

后面才 换成了项目中本来使用的 jpa 的一些 api 来使用 

 

呵呵 使用 jdbcTemlpate.query 来查询, 你会发现完全没有问题, 呵呵 没有被校验住, 并且拿到了最终的数据结果 3 条记录, 去掉了三个是用于测试效果

最开始我在想 这里是给出了完整的 sql, 那么 它是不是走的 协议中所说的简单查询, 所以它没有传递参数数量阿, 这些 所以没有报错呢? 但是实际看了一下 呵呵 和我想的不一样 

watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L3UwMTEwMzkzMzI=,size_16,color_FFFFFF,t_70

 

它还是走的时候 扩展查询的这一系列的步骤, 呵呵 只是这里 encodedSize, parameterCount, 以及一些 其他的限制都在合理的范围内, 传递到了服务器那边 去执行 

watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L3UwMTEwMzkzMzI=,size_16,color_FFFFFF,t_70

 

 

完 

 

 

参考

章44. 前/后端协议 

44.2.3. 扩展查询

44.4. 消息格式 

 

 

 

相关推荐

  1. ORACLE:VARCHAR2(4000)太小怎么办?

    2024-03-21 16:42:03       39 阅读
  2. 华为od 100问 持续分享-2

    2024-03-21 16:42:03       31 阅读
  3. Oracle 数据库 varchar24000 扩展到 32k

    2024-03-21 16:42:03       31 阅读
  4. cpt111 Principles of Programming

    2024-03-21 16:42:03       36 阅读
  5. xtu oj 1150 n!进制 2.0

    2024-03-21 16:42:03       49 阅读
  6. fin11 Cost of Capital

    2024-03-21 16:42:03       41 阅读
  7. LeetCode160. Intersection of Two Linked Lists

    2024-03-21 16:42:03       45 阅读
  8. [NeetCode 150] Products of Array Discluding Self

    2024-03-21 16:42:03       26 阅读

最近更新

  1. docker php8.1+nginx base 镜像 dockerfile 配置

    2024-03-21 16:42:03       98 阅读
  2. Could not load dynamic library ‘cudart64_100.dll‘

    2024-03-21 16:42:03       106 阅读
  3. 在Django里面运行非项目文件

    2024-03-21 16:42:03       87 阅读
  4. Python语言-面向对象

    2024-03-21 16:42:03       96 阅读

热门阅读

  1. C++ ostringstream用法详解

    2024-03-21 16:42:03       41 阅读
  2. Querywrapper与Lambdaquerywrappe比较

    2024-03-21 16:42:03       43 阅读
  3. PHP用户定义函数讲解

    2024-03-21 16:42:03       45 阅读
  4. 【学习笔记】如何实现云原生初步

    2024-03-21 16:42:03       38 阅读
  5. C#-面向对象基本概念

    2024-03-21 16:42:03       45 阅读
  6. sxf-漏洞研究员实习

    2024-03-21 16:42:03       40 阅读
  7. Tastewise推出AI解决方案TasteGPT

    2024-03-21 16:42:03       42 阅读
  8. git 不同仓库 同步

    2024-03-21 16:42:03       39 阅读