首页
学习
活动
专区
圈层
工具
发布
首页
学习
活动
专区
圈层
工具
MCP广场
社区首页 >专栏 >分布式 | 中间件是如何处理 Prepare Statement 和游标的

分布式 | 中间件是如何处理 Prepare Statement 和游标的

作者头像
爱可生开源社区
发布于 2022-01-04 11:17:41
发布于 2022-01-04 11:17:41
79100
代码可运行
举报
运行总次数:0
代码可运行

作者:董诚怡

爱可生 dble 团队开发成员,主要负责 dble 需求开发,故障排查和社区问题解答。

本文来源:原创投稿

*爱可生开源社区出品,原创内容未经授权不得随意使用,转载请联系小编并注明来源。

PS语句(预编译语句)

以下用 缩写 PS 代替 Prepare Statement

PS 的优点
  • 防止SQL注入漏洞
  • 标准的 PS 可以 一次编译、多次运行,省去了每次都要解析优化的过程。(被称为预编译,有利于多次执行的结构相同参数不同的语句)
  • 更灵活的调用
PS 的缺点
  • 普通的查询(即时 SQL ) 需要一次网络开销,但是 PS 需要至少两次网络开销。如果游标的话,次数将更多,和数据量成正比。
  • 需要空间开销用于缓存当前的 SQL 预编译后的结构,相对于即时 SQL ,这块空间不能及时释放。
  • 对 in 语句的支持不太好
分类
  • server-side PS:通过 client 发送 PS 协议的报文给 server ,由 server 来完成拼装参数、优化、执行。
  • client-side PS: 由 client 来实现 PS 接口,prepare 阶段完成拼装参数,拼装完后,一次性发送 即时 SQL 给 server ,由 server 来完成优化、执行。这本质上是一个伪预编译,上述的 ”省去了每次都要解析优化的过程“ 这个优点无法实现。
使用 dble 侧 PS 必要条件

DBLE 端

客户端

  • 如果是 JDBC 需开启 useServerPrepStmts ,此时才会使用 server-side prepare ,否则属于 client-side prepare 。

验证是否开启了

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
PreparedStatement preparedStatement = con.prepareStatement("select t1.id from no_sharding_t1 t1 where t1.id=?"); 
//可用于验证是否使用了dble 侧 prepare
assert preparedStatement instanceof ServerPreparedStatement;
协议
  • COM_STMT_PREPARE 发送:SQL 作用:创建一个 statement ,完成预编译,执行优化等准备工作,等待下一步执行。 响应: statement 的 id 以及 column 的数量、argument 数量和类型
  • COM_STMT_EXECUTE 发送:statement 的 id ,每个占位符绑定的值,以及是否想要开启游标 作用:执行 SQL 响应:OK 响应或者结果集
  • COM_STMT_FETCH 发送:statement 的 id ,期望获取的行数 作用:分批次获取执行后数据 响应:指定行数的结果集 注意:该协议可选,也可以在 execute 环节上返回结果集,dble 一般仅在开启游标时使用该报文
  • COM_STMT_SEND_LONG_DATA 作用:发送占位符绑定的值,通常只用于发送 BLOB 数据
  • COM_STMT_RESET 作用:重置 COM_STMT_SEND_LONG_DATA 设置的值 (通常不用)
  • COM_STMT_CLOSE 发送:statement 的 id 作用:关闭之前的 Preapred Statement ,回收所有资源 响应:无

流程图:

可以看到 client <-> dble 通讯使用了 server-side prepare,dble <-> MySQL 通讯使用了 client-side prepare,也就是说后端通讯和普通的即时查询无异,只是需要做一些协议上的包的转换。

原理
  • prepare 阶段将语句暂存,不进行预编译
  • execute 阶段拼装参数和语句,将其下发,获得结果后转换为 PS 协议并返回

游标

游标 的优点
  • 对于客户端较友好,不会因为大查询而 OOM
游标 的缺点
  • 较慢
  • 资源不及时释放
分类
  • server-side cursor:server 把结果集暂存起来,维护一个游标,client 根据需要读取指定的行数
  • client-side cursor: client 从 TCP 层面控制报文的读取,对报文进行流量控制,当报文太多时暂停读取。(不推荐,因为 server 需要等待所有数据发送给 client 后,才能释放资源。)
  • 另一种 client-side cursor:client 把所有结果集读取到本地缓存,client 每次从缓存读取指定行数(不推荐,本质上是个伪 cursor,只实现了 cursor API。并且在数据量较大时很容易撑爆 client 的内存)

后两种是否支持取决于 client 端的 driver,dble 支持的是第一种 server-side cursor。

游标开启必要条件

DBLE 端

注:读写分离场景由于不支持COM_STMT_FETCH报文,所以不支持游标。以下描述仅针对分库分表。

  • 如果版本<3.21.02, 则不支持。
  • 如果版本=3.21.02,无需设置
  • 如果版本>3.21.02,需在 bootstrap.cnf 开启-DenableCursor=false

客户端

  1. 使用支持游标的 driver(MySQL 官方的 JDBC driver 就支持)
  2. 如果是 JDBC 需开启 useServerPrepStmts 和 useCursorFetch 选项
  3. 执行 prepareStatement 后设置 fetchSize,必须大于 0.
  4. 执行 execute

此时是开启游标的,如果对结果集 resultSet 进行遍历,会按 fetchSize 的大小一次次地从 dble 取回数据。

验证是否开启了游标

客户端执行第4步后, 调用私有方法 useServerFetch 可验证。

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
final ResultSet resultSet = preparedStatement.executeQuery();
//可用于验证是否使用了server-side 游标
Method method = com.mysql.cj.jdbc.StatementImpl.class.getDeclaredMethod("useServerFetch");
method.setAccessible(true);
Boolean useServerFetch = (Boolean) method.invoke(preparedStatement);
assert useServerFetch==true;

流程图:

原理:
  1. prepare 阶段下发特殊语句。用于计算 SQL 中的列数,这是 client 所需的开启游标的必要条件。
  2. execute 阶段把结果集存储到临时文件
  3. fetch 阶段把结果集分批次取出来
相关参数

maxHeapTableSize

heapTableBufferChunkSize

见文档 https://actiontech.github.io/dble-docs-cn/1.config_file/1.02_bootstrap.cnf.html

本文参与 腾讯云自媒体同步曝光计划,分享自微信公众号。
原始发表:2021-12-29,如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 爱可生开源社区 微信公众号,前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体同步曝光计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档
本文部分代码块支持一键运行,欢迎体验
本文部分代码块支持一键运行,欢迎体验