MySQL驱动Add Batch优化实现
MySQL 驱动 Add Batch 优化实现
MySQL 驱动会在 JDBC URL 添加 rewriteBatchedStatements 参数时,对 batch 操作进行优化。本文测试各种参数组合的行为,并结合驱动代码简单分析。
batch参数组合行为
useServerPrepStmts 参数
PreparedStatement psmt = connection.prepareStatement("DELETE FROM t_order WHERE `order_id` = ?");
psmt.setObject(1, 1);
psmt.execute();
开启:
使用服务端预编译,先发送 prepared 语句,再发送 excute 语句
不开启:
mysql 驱动会将占位符填充后,明文下发sql.
比如 DELETE FROM t_order WHERE order_id
= ?; 语句
MySQL 驱动会下发 DELETE FROM t_order WHERE order_id
= 1;
allowMultiQueries 参数
Statement statement = connection.createStatement();
statement.execute("DELETE FROM t_order WHERE `order_id` = 1;DELETE FROM t_order WHERE `order_id` = 2;");
不开启:
服务端不支持 DELETE FROM t_order WHERE order_id
= 1;DELETE FROM t_order WHERE order_id
= 2; 这样的批量delete语句
开启:
支持多语句,比如: DELETE FROM t_order WHERE order_id
= 1;DELETE FROM t_order WHERE order_id
= 2;
rewriteBatchedStatements 参数
PreparedStatement psmt = connection.prepareStatement("DELETE FROM t_order WHERE `order_id` = ?");
for (int i = 1; i <= 500; i++) {psmt.setObject(1, i);psmt.addBatch();
}
psmt.executeBatch();
不开启:
batch 操作,在 addBatch 时一条条下发参数值。
开启:
在执行 executeBatch 时,将 batch 操作改写后批量下发;改写后的 SQL 比如 DELETE FROM t_order WHERE order_id
= 1;DELETE FROM t_order WHERE order_id
= 2;…;DELETE FROM t_order WHERE order_id
= 500;
useServerPrepStmts+allowMultiQueries 参数
PreparedStatement psmt = connection.prepareStatement("DELETE FROM t_order WHERE `order_id` = ?;DELETE FROM t_order WHERE `order_id` = ?;");
psmt.execute();
虽然开启了服务端预编译参数 useServerPrepStmts,但是 MySQL JDBC 驱动会判断预编译 SQL 不支持 allowMultiQueries,会直接转换成客户端预编译,也就时会将占位符赋值后下发到 mysql。
比如对于以下 SQL: DELETE FROM t_order WHERE order_id
= ?;DELETE FROM t_order WHERE order_id
= ?;
客户端会将占位符填充后发送: DELETE FROM t_order WHERE order_id
= 1;DELETE FROM t_order WHERE order_id
= 2;
useServerPrepStmts+rewriteBatchedStatements 参数
PreparedStatement psmt = connection.prepareStatement("DELETE FROM t_order WHERE `order_id` = ?");
for (int i = 1; i < 500; i++) {psmt.setObject(1, i);psmt.addBatch();
}
psmt.executeBatch();
对于 DELETE FROM t_order WHERE order_id
= ? 的 batch 语句,
会转换成 DELETE FROM t_order WHERE order_id
= ?;DELETE FROM t_order WHERE order_id
= ?;多语句下发,但是服务端返回不支持。
然后客户端再使用客户端预编译尝试发送(会先发送 set multi option on 包,执行完再关闭该标识),然后下发多语句 DELETE FROM t_order WHERE order_id
= 1;DELETE FROM t_order WHERE order_id
= 2;…;DELETE FROM t_order WHERE order_id
= 500;
useServerPrepStmts+allowMultiQueries+rewriteBatchedStatements 参数
PreparedStatement psmt = connection.prepareStatement("DELETE FROM t_order WHERE `order_id` = ?");
for (int i = 1; i < 500; i++) {psmt.setObject(1, i);psmt.addBatch();
}
psmt.executeBatch();
当执行 batch delete 语句时:
虽然开启了 useServerPrepStmts 预编译参数,但是 MySQL JDBC 驱动会判断预编译 SQL 不支持 allowMultiQueries,会直接转换成客户端预编译,也就将占位符赋值后下发到服务端。
也就是发送 DELETE FROM t_order WHERE order_id
= 1;DELETE FROM t_order WHERE order_id
= 2;…;DELETE FROM t_order WHERE order_id
= 500;
MySQL 驱动代码分析
下面结合驱动代码简单分析两组参数的流程:
1.useServerPrepStmts+rewriteBatchedStatements+allowMultiQueries 参数
// 测试 demo
// jdbc:mysql://localhost:3306/test?serverTimezone=UTC&useSSL=false&allowPublicKeyRetrieval=true&useServerPrepStmts=true&cachePrepStmts=true&prepStmtCacheSize=512&prepStmtCacheSqlLimit=8000&rewriteBatchedStatements=true&allowMultiQueries=true
PreparedStatement psmt = connection.prepareStatement("DELETE FROM t_order WHERE `order_id` = ?");
for (int i = 1; i < 500; i++) {psmt.setObject(1, i);psmt.addBatch();
}
psmt.executeBatch();
MySQL驱动处理逻辑:
1.prepareStatement
当执行 prepareStatement() 时,因为开启了 useServerPrepStmts 参数,所以会下发预编译 sql 给 server 端。
PreparedStatement psmt = connection.prepareStatement(sql);
相关调用栈如下:
com.mysql.cj.jdbc.ConnectionImpl#prepareStatementcom.mysql.cj.jdbc.ConnectionImpl#canHandleAsServerPreparedStatement1.判断当前sql是否支持服务端预编译com.mysql.cj.jdbc.ServerPreparedStatement#getInstancecom.mysql.cj.jdbc.ServerPreparedStatement#serverPrepare2.下发预编译sqlcom.mysql.cj.ServerPreparedQuery#serverPreparecom.mysql.cj.protocol.a.NativeMessageBuilder#buildComStmtPreparecom.mysql.cj.NativeSession#sendCommand发包com.mysql.cj.protocol.a.NativeProtocol#readcom.mysql.cj.protocol.a.ColumnDefinitionReader#unpackField
1.开启 useServerPrepStmts 参数(emulateUnsupportedPstmts 参数默认就为true),会再根据 canHandleAsServerPreparedStatement() 判断当前 sql 是否支持服务端预编译。
2.canHandleAsServerPreparedStatement()方法会通过 StringUtils._canHandleAsServerPreparedStatementNoCache()
方法检测 sql 是否可以支持服务端预编译。
com.mysql.cj.jdbc.ServerPreparedStatement#getInstance
发送预编译 sql
2.addBatch
添加参数即可,不和服务端交互。
3.executeBatch
调用栈:
com.mysql.cj.jdbc.StatementImpl#executeBatchcom.mysql.cj.jdbc.ClientPreparedStatement#executeBatchInternalcom.mysql.cj.jdbc.StatementImpl#statementBeginscom.mysql.cj.ServerPreparedQuery#statementBeginscom.mysql.cj.AbstractQuery#statementBeginscom.mysql.cj.AbstractPreparedQuery#getParseInfocom.mysql.cj.ParseInfo#canRewriteAsMultiValueInsertAtSqlLevel 是否支持insert values改写优化com.mysql.cj.jdbc.ClientPreparedStatement#executePreparedBatchAsMultiStatement 多语句执行优化com.mysql.cj.AbstractQuery#getBatchedArgscom.mysql.cj.AbstractPreparedQuery#computeBatchSize 计算批次大小com.mysql.cj.ServerPreparedQuery#computeMaxParameterSetSizeAndBatchSizecom.mysql.cj.jdbc.ClientPreparedStatement#generateMultiStatementForBatchcom.mysql.cj.jdbc.ConnectionImpl#prepareStatementcom.mysql.cj.jdbc.ConnectionImpl#prepareStatementcom.mysql.cj.jdbc.ServerPreparedStatement#setOneBatchedParameterSetcom.mysql.cj.jdbc.ClientPreparedStatement#executecom.mysql.cj.jdbc.ClientPreparedStatement#checkReadOnlySafeStatementcom.mysql.cj.protocol.a.NativeMessageBuilder#buildComQuerycom.mysql.cj.NativeSession#sendCommand
- 由于开启了 rewriteBatchedStatements 参数,并且 delelte batch size 大于 3,会通过 executePreparedBatchAsMultiStatement 优化成 批量 delete.
// 开启 rewriteBatchedStatements 参数
if (!this.batchHasPlainStatements && this.rewriteBatchedStatements.getValue()) {// batch size > 3,则将 sql 转成 multi deleteif (!this.batchHasPlainStatements && this.query.getBatchedArgs() != null&& this.query.getBatchedArgs().size() > 3 /* cost of option setting rt-wise */) {return executePreparedBatchAsMultiStatement(batchTimeout);}
}
- executePreparedBatchAsMultiStatement 方法
计算需要分几个批次下发。正常500条sql一个批次即可。
// 根据上面得到的batch sql的长度,确定分几个批次下发 multi sql;
/*** Computes the optimum number of batched parameter lists to send* without overflowing max_allowed_packet.* * @param numBatchedArgs* original batch size* @return computed batch size*/
public int computeBatchSize(int numBatchedArgs) {long[] combinedValues = computeMaxParameterSetSizeAndBatchSize(numBatchedArgs);long maxSizeOfParameterSet = combinedValues[0];long sizeOfEntireBatch = combinedValues[1];// 整个 batch sql 的长度不能超长,正常走到这个分支里if (sizeOfEntireBatch < this.maxAllowedPacket.getValue() - this.originalSql.length()) {return numBatchedArgs;}return (int) Math.max(1, (this.maxAllowedPacket.getValue() - this.originalSql.length()) / maxSizeOfParameterSet);
}
计算方法如下:
// 计算 maxSizeOfParameterSet: 每个参数中最大的长度
// sizeOfEntireBatch: batch所有参数长度相加
/*** Computes the maximum parameter set size and the size of the entire batch given* the number of arguments in the batch.*/
@Override
protected long[] computeMaxParameterSetSizeAndBatchSize(int numBatchedArgs) {long sizeOfEntireBatch = 1 + /* com_execute */+4 /* stmt id */ + 1 /* flags */ + 4 /* batch count padding */;long maxSizeOfParameterSet = 0;for (int i = 0; i < numBatchedArgs; i++) {ServerPreparedQueryBindValue[] paramArg = ((ServerPreparedQueryBindings) this.batchedArgs.get(i)).getBindValues();long sizeOfParameterSet = (this.parameterCount + 7) / 8; // for isNullsizeOfParameterSet += this.parameterCount * 2; // have to send typesServerPreparedQueryBindValue[] parameterBindings = this.queryBindings.getBindValues();for (int j = 0; j < parameterBindings.length; j++) {if (!paramArg[j].isNull()) {long size = paramArg[j].getBoundLength();if (paramArg[j].isStream()) {if (size != -1) {sizeOfParameterSet += size;}} else {sizeOfParameterSet += size;}}}sizeOfEntireBatch += sizeOfParameterSet;if (sizeOfParameterSet > maxSizeOfParameterSet) {maxSizeOfParameterSet = sizeOfParameterSet;}}return new long[] { maxSizeOfParameterSet, sizeOfEntireBatch };
}
- 拼接预编译sql,此时还有?号占位符
((Wrapper) locallyScopedConn.prepareStatement(generateMultiStatementForBatch(numValuesPerBatch))).unwrap(java.sql.PreparedStatement.class);
得到sql如下:
DELETE FROM t_order WHERE `order_id` = ?;DELETE FROM t_order WHERE `order_id` = ?;......
- 执行 prepareStatement,将上面的批量delete语句执行预编译。
这里逻辑和第一步里的 prepareStatement 类似,也需要判断当前批量delete sql是否支持服务端预编译。
这里可以看到,开启 allowMultiQueries 参数之后,驱动会查找 sql 里是否包含 ;
号,如果包含,不支持服务端预编译。后面会走客户端预编译流程。
- 客户端预编译执行批量delete
- 设置参数
// 给?号占位符塞值
batchedParamIndex = setOneBatchedParameterSet(batchedStatement, batchedParamIndex, this.query.getBatchedArgs().get(batchCounter++));
- 执行批量delete语句
2.useServerPrepStmts+rewriteBatchedStatements 参数
1.下发预编译sql
2.设置 multi statement on
3.多语句预编译sql,执行返回失败
You have an error in your SQL syntax; check the manual that corresponds to your MySQL server version for the right syntax to use near 'DELETE FROM t_order WHERE order_id
= ?;DELETE FROM t_order WHERE order_id
’ at line 1
代码就是服务端prepared返回失败,会再用 client Prepare statement 重试
4.client Prepare statement 重新批量delete 语句
相关文章:

MySQL驱动Add Batch优化实现
MySQL 驱动 Add Batch 优化实现 MySQL 驱动会在 JDBC URL 添加 rewriteBatchedStatements 参数时,对 batch 操作进行优化。本文测试各种参数组合的行为,并结合驱动代码简单分析。 batch参数组合行为 useServerPrepStmts 参数 PreparedStatement psmt…...

手撕算法-数组中的第K个最大元素
描述 分析 使用小根堆,堆元素控制在k个,遍历数组构建堆,最后堆顶就是第K个最大的元素。 代码 class Solution {public int findKthLargest(int[] nums, int k) {// 小根堆PriorityQueue<Integer> queue new PriorityQueue<>…...
【vue】computed和watch的区别和应用场景
Computed 和 Watch 是 Vue.js 中用于监视数据变化的两个不同特性,它们各自有不同的应用场景和功能。 Computed: 计算属性(Computed properties)用于声明基于其他数据属性的计算值。它具有缓存功能,只有在依赖的数…...
ARM.day8
1.自己设置温度湿度阈值,当温度过高时,打开风扇,蜂鸣器报警 2.当湿度比较高时,打开LED1灯,蜂鸣器报警 main.c #include "si7006.h" #include "CH1.h" #include "led.h" // 延时函数in…...

SpringCloud Gateway工作流程
Spring Cloud Gateway的工作流程 具体的流程: 用户发送请求到网关 请求断言,用户请求到达网关后,由Gateway Handler Mapping(网关处理器映射)进行Predicates(断言),看一下哪一个符合…...

西井科技与安通控股签署战略合作协议 共创大物流全新生态
2024年3月21日,西井科技与安通控股在“上海硅巷”新象限空间正式签署战略合作框架协议。双方基于此前在集装箱物流的成功实践与资源优势,积极拓展在AI数字化产品、新能源自动驾驶解决方案和多场景应用,以及绿色物流链等领域的深度探索、强强联…...
CCCorelib 点云RANSAC拟合球体(CloudCompare内置算法库)
文章目录 一、简介二、实现代码三、实现效果参考资料一、简介 RANSAC是一种随机参数估计算法。RANSAC从样本中随机抽选出一个样本子集,使用最小方差估计算法对这个子集计算模型参数,然后计算所有样本与该模型的偏差,再使用一个预先设定好的阈值与偏差比较,当偏差小于阈值时…...

map china not exists. the geojson of the map must be provided.
map china not exists. the geojson of the map must be provided. 场景:引入echarts地图报错map china not exists. the geojson of the map must be provided. 原因: echarts版本过高,ECharts 之前提供下载的矢量地图数据来自第三方&…...

Redis如何删除大key
参考阿里云Redis规范 查找大key: redis-cli --bigkeys 1、String类型: Redis 4.0及以后版本提供了UNLINK命令,该命令与DEL命令类似,但它会在后台异步删除key,不会阻塞当前客户端,也不会阻塞Redis服务器的…...

JRT菜单
上一章搭建了登录界面的雏形和抽取了登录接口。给多组使用登录和菜单功能提供预留,做到不强行入侵别人业务。任何产品只需要按自己表实现登录接口后配置到容器即可共用登录界面和菜单部分。最后自己的用户关联到JRT角色表即可。 登录效果 这次构建菜单体系 首先用…...

《海王2》观后感
前言 我原本计划电影上映之后,去电影院观看的,但时间过得飞快,一眨眼这都快4月份了,查了一下,电影院早就没有排片了,所以只能在B站看了,这里不得不吐槽一下,原来花了4块钱购买观看还…...

[蓝桥杯 2023 省 A] 颜色平衡树:从零开始理解树上莫队 一颗颜色平衡树引发的惨案
十四是一名生物工程的学生,他已经7年没碰过信息学竞赛了,有一天他走在蓝桥上看见了一颗漂亮的颜色平衡树: [蓝桥杯 2023 省 A] 颜色平衡树 - 洛谷 十四想用暴力解决问题,他想枚举每个节点,每个节点代表…...

maya打开bvh脚本
目录 maya打开脚本编辑器 运行打开bvh脚本 maya导出bvh脚本 maya打开脚本编辑器 打开Maya软件,点击右下角 “脚本编辑器” 运行打开bvh脚本 https://github.com/jhoolmans/mayaImporterBVH/blob/master/bvh_importer.py import os import re from typing impo…...

【JavaSE】数据类型和运算符
前言 从这一篇我们开始Java的学习~ 欢迎关注个人主页:逸狼 创造不易,可以点点赞吗~ 如有错误,欢迎指出~ 目录 前言 Java第一个程序 字面常量 字面常量的分类 结合代码理解 类型转换 类型提升 byte与byte的运算 正确写法 字符串类型St…...
Docker 哲学 - ip 的组成规则 与 网关介绍
在 IP 地址中,我们通常将 IP 地址分为两部分:网络部分和主机部分。网络部分用于标识网络,主机部分用于标识该网络中的特定主机。 IP 地址的每个部分(也被称为一个八位组或一个字节)可以是从0到255的任何值。 一个 IPv4…...

数学建模竞赛真的是模型解题一般,但是论文出彩而获奖的吗?
最近,数乐君发现有同学会有这样的问题:在数学建模国赛中,会因为参赛团队的模型解题一般,但论文写得非常精彩而获奖吗? 是的,确实会存在这样的情况。 我们都知道数学建模竞赛最终都是以提交成品论文的形式…...
深度学习常见的三种模型
深度学习模型实际上是一个包含多个隐藏层的神经网络,目前主要有卷积神经网络(CNN)、深度置信网络(DBN)、循环神经网络(RNN)。 1) 卷积神经网络 在机器学习领域,卷积神经网络属于前…...
接口自动化测试分层设计与实践总结
🍅 视频学习:文末有免费的配套视频可观看 🍅 关注公众号:互联网杂货铺,回复1 ,免费获取软件测试全套资料,资料在手,涨薪更快 接口测试三要素: 参数构造 发起请求&#x…...

集合(下)Map集合的使用
文章目录 前言一、Map接口二、Map接口的实现类 1.HashMap类2.TreeMap类总结 前言 Map集合没有继承Collection接口,不能像List集合和Set集合那样直接使用Collection接口的方法。Map集合其自身通过以key到value的映射关系实现的集合,也有相应的许多方法。类…...
AAPT: error: resource android:attr/dialogCornerRadius not found.
ERROR:D:\android.gradle\caches\transforms-3\b3b98118f65da38d0ad9da84cfc70a72\transformed\appcompat-1.0.0\res\values-v28\values-v28.xml:5:5-8:13: AAPT: error: resource android:attr/dialogCornerRadius not found. 请帮我看看这个错误是什么意思。我改如何做。 这个…...

C++实现分布式网络通信框架RPC(3)--rpc调用端
目录 一、前言 二、UserServiceRpc_Stub 三、 CallMethod方法的重写 头文件 实现 四、rpc调用端的调用 实现 五、 google::protobuf::RpcController *controller 头文件 实现 六、总结 一、前言 在前边的文章中,我们已经大致实现了rpc服务端的各项功能代…...

Docker 运行 Kafka 带 SASL 认证教程
Docker 运行 Kafka 带 SASL 认证教程 Docker 运行 Kafka 带 SASL 认证教程一、说明二、环境准备三、编写 Docker Compose 和 jaas文件docker-compose.yml代码说明:server_jaas.conf 四、启动服务五、验证服务六、连接kafka服务七、总结 Docker 运行 Kafka 带 SASL 认…...

Python实现prophet 理论及参数优化
文章目录 Prophet理论及模型参数介绍Python代码完整实现prophet 添加外部数据进行模型优化 之前初步学习prophet的时候,写过一篇简单实现,后期随着对该模型的深入研究,本次记录涉及到prophet 的公式以及参数调优,从公式可以更直观…...
【git】把本地更改提交远程新分支feature_g
创建并切换新分支 git checkout -b feature_g 添加并提交更改 git add . git commit -m “实现图片上传功能” 推送到远程 git push -u origin feature_g...

零基础设计模式——行为型模式 - 责任链模式
第四部分:行为型模式 - 责任链模式 (Chain of Responsibility Pattern) 欢迎来到行为型模式的学习!行为型模式关注对象之间的职责分配、算法封装和对象间的交互。我们将学习的第一个行为型模式是责任链模式。 核心思想:使多个对象都有机会处…...

在WSL2的Ubuntu镜像中安装Docker
Docker官网链接: https://docs.docker.com/engine/install/ubuntu/ 1、运行以下命令卸载所有冲突的软件包: for pkg in docker.io docker-doc docker-compose docker-compose-v2 podman-docker containerd runc; do sudo apt-get remove $pkg; done2、设置Docker…...
大语言模型(LLM)中的KV缓存压缩与动态稀疏注意力机制设计
随着大语言模型(LLM)参数规模的增长,推理阶段的内存占用和计算复杂度成为核心挑战。传统注意力机制的计算复杂度随序列长度呈二次方增长,而KV缓存的内存消耗可能高达数十GB(例如Llama2-7B处理100K token时需50GB内存&a…...

C++:多态机制详解
目录 一. 多态的概念 1.静态多态(编译时多态) 二.动态多态的定义及实现 1.多态的构成条件 2.虚函数 3.虚函数的重写/覆盖 4.虚函数重写的一些其他问题 1).协变 2).析构函数的重写 5.override 和 final关键字 1&#…...
省略号和可变参数模板
本文主要介绍如何展开可变参数的参数包 1.C语言的va_list展开可变参数 #include <iostream> #include <cstdarg>void printNumbers(int count, ...) {// 声明va_list类型的变量va_list args;// 使用va_start将可变参数写入变量argsva_start(args, count);for (in…...

Rust 开发环境搭建
环境搭建 1、开发工具RustRover 或者vs code 2、Cygwin64 安装 https://cygwin.com/install.html 在工具终端执行: rustup toolchain install stable-x86_64-pc-windows-gnu rustup default stable-x86_64-pc-windows-gnu 2、Hello World fn main() { println…...