MySQL驱动Add Batch优化实现

news2024/10/8 11:09:22

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#prepareStatement
    com.mysql.cj.jdbc.ConnectionImpl#canHandleAsServerPreparedStatement
    1.判断当前sql是否支持服务端预编译
    com.mysql.cj.jdbc.ServerPreparedStatement#getInstance
        com.mysql.cj.jdbc.ServerPreparedStatement#serverPrepare
        2.下发预编译sql
            com.mysql.cj.ServerPreparedQuery#serverPrepare
                com.mysql.cj.protocol.a.NativeMessageBuilder#buildComStmtPrepare
                com.mysql.cj.NativeSession#sendCommand
                发包
                com.mysql.cj.protocol.a.NativeProtocol#read
                    com.mysql.cj.protocol.a.ColumnDefinitionReader#unpackField

1.开启 useServerPrepStmts 参数(emulateUnsupportedPstmts 参数默认就为true),会再根据 canHandleAsServerPreparedStatement() 判断当前 sql 是否支持服务端预编译。

![[img-20240325200018-1.jpg]]

2.canHandleAsServerPreparedStatement()方法会通过 StringUtils._canHandleAsServerPreparedStatementNoCache() 方法检测 sql 是否可以支持服务端预编译。
在这里插入图片描述

  1. com.mysql.cj.jdbc.ServerPreparedStatement#getInstance 发送预编译 sql

![[img-20240325200019-3.jpg]]

2.addBatch

添加参数即可,不和服务端交互。

3.executeBatch

调用栈:

com.mysql.cj.jdbc.StatementImpl#executeBatch
    com.mysql.cj.jdbc.ClientPreparedStatement#executeBatchInternal
        com.mysql.cj.jdbc.StatementImpl#statementBegins
            com.mysql.cj.ServerPreparedQuery#statementBegins
                com.mysql.cj.AbstractQuery#statementBegins
        com.mysql.cj.AbstractPreparedQuery#getParseInfo
        com.mysql.cj.ParseInfo#canRewriteAsMultiValueInsertAtSqlLevel 是否支持insert values改写优化
        com.mysql.cj.jdbc.ClientPreparedStatement#executePreparedBatchAsMultiStatement 多语句执行优化
            com.mysql.cj.AbstractQuery#getBatchedArgs
            com.mysql.cj.AbstractPreparedQuery#computeBatchSize 计算批次大小
                com.mysql.cj.ServerPreparedQuery#computeMaxParameterSetSizeAndBatchSize
            com.mysql.cj.jdbc.ClientPreparedStatement#generateMultiStatementForBatch
            com.mysql.cj.jdbc.ConnectionImpl#prepareStatement
                com.mysql.cj.jdbc.ConnectionImpl#prepareStatement
            com.mysql.cj.jdbc.ServerPreparedStatement#setOneBatchedParameterSet
            com.mysql.cj.jdbc.ClientPreparedStatement#execute
                com.mysql.cj.jdbc.ClientPreparedStatement#checkReadOnlySafeStatement
                    com.mysql.cj.protocol.a.NativeMessageBuilder#buildComQuery
                    com.mysql.cj.NativeSession#sendCommand
  1. 由于开启了 rewriteBatchedStatements 参数,并且 delelte batch size 大于 3,会通过 executePreparedBatchAsMultiStatement 优化成 批量 delete.
// 开启 rewriteBatchedStatements 参数
if (!this.batchHasPlainStatements && this.rewriteBatchedStatements.getValue()) {
    // batch size > 3,则将 sql 转成 multi delete
    if (!this.batchHasPlainStatements && this.query.getBatchedArgs() != null
            && this.query.getBatchedArgs().size() > 3 /* cost of option setting rt-wise */) {
        return executePreparedBatchAsMultiStatement(batchTimeout);
    }
}
  1. 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 isNull
        sizeOfParameterSet += this.parameterCount * 2; // have to send types

        ServerPreparedQueryBindValue[] 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 };
}
  1. 拼接预编译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` = ?;......
  1. 执行 prepareStatement,将上面的批量delete语句执行预编译。

这里逻辑和第一步里的 prepareStatement 类似,也需要判断当前批量delete sql是否支持服务端预编译。

这里可以看到,开启 allowMultiQueries 参数之后,驱动会查找 sql 里是否包含 ; 号,如果包含,不支持服务端预编译。后面会走客户端预编译流程。

在这里插入图片描述

  1. 客户端预编译执行批量delete

在这里插入图片描述

  1. 设置参数
// 给?号占位符塞值
batchedParamIndex = setOneBatchedParameterSet(batchedStatement, batchedParamIndex, this.query.getBatchedArgs().get(batchCounter++));
  1. 执行批量delete语句

在这里插入图片描述

在这里插入图片描述

2.useServerPrepStmts+rewriteBatchedStatements 参数

1.下发预编译sql

![[img-20240325200021-9.jpg]]

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 语句

在这里插入图片描述

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/1554821.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

C# wpf 实现底部嵌入HwndHost

WPF Hwnd窗口互操作系列 第一章 嵌入Hwnd窗口 第二章 嵌入WinForm控件 第三章 嵌入WPF控件 第四章 底部嵌入HwndHost&#xff08;本章&#xff09; 文章目录 WPF Hwnd窗口互操作系列前言一、如何实现&#xff1f;1、底部创建窗口&#xff08;1&#xff09;、创建透明窗口&…

[flink 实时流基础]源算子和转换算子

文章目录 1. 源算子 Source1. 从集合读2. 从文件读取3. 从 socket 读取4. 从 kafka 读取5. 从数据生成器读取数据 2. 转换算子基本转换算子&#xff08;map/ filter/ flatMap&#xff09; 1. 源算子 Source Flink可以从各种来源获取数据&#xff0c;然后构建DataStream进行转换…

ios应用内支付

用uniapp开发iOS应用内支付 准备前端代码服务器端处理如果iOS支付遇到问题实在解决不了&#xff0c;可以联系我帮忙解决&#xff0c;前端后端都可以解决&#xff08;添加的时候一定要备注咨询iOS支付问题&#xff09; 准备前端代码 获取支付通道 (uni.getProvider) uni.getPr…

怎么更新sd-webui AUTOMATIC1111/stable-diffusion-webui ?

整个工程依靠脚本起来的&#xff1a; 可直接到stable-diffusion-webui子目录执行&#xff1a; git pull更新代码完毕后&#xff0c;删除venv的虚拟环境。 然后再次执行webui.sh&#xff0c;这样会自动重新启动stable-diffusion-webui.

Netty核心原理剖析与RPC实践21-25

Netty核心原理剖析与RPC实践21-25 21 技巧篇&#xff1a;延迟任务处理神器之时间轮 HahedWheelTimer Netty 中有很多场景依赖定时任务实现&#xff0c;比较典型的有客户端连接的超时控制、通信双方连接的心跳检测等场景。在学习 Netty Reactor 线程模型时&#xff0c;我们知道…

卸载原有的cuda,更新cuda

概述&#xff1a;看了一下自己的gpu&#xff0c;发现驱动可能装低了&#xff0c;随即尝试更新驱动&#xff0c;写下此篇 注&#xff1a;我原先是10.2的版本&#xff0c;改了之后是11.2&#xff0c;下面的图都用11.2的&#xff0c;不过不碍事 目录 第一步&#xff1a;查看现在…

SmartChart的部署以及可能遇见的报错解决方案

简介 数据可视化是一种将数据转化为图形的技术&#xff0c;可以帮助人们更好地理解和分析数据。但是&#xff0c;传统的数据可视化开发往往需要编写大量的代码&#xff0c;或者使用复杂的拖拽工具&#xff0c;不仅耗时耗力&#xff0c;而且难以实现个性化的需求。有没有一种更…

深度解析:Elasticsearch检索请求原理

在上一篇文章中&#xff0c;我们学习了 Elasticsearch 的写入流程&#xff0c;今天我们来学习一下 Elasticsearch 的读取流程&#xff0c;当一个检索请求到达 Elasticsearch 之后是如何进行检索的呢&#xff1f; 下面先说一下一个总的检索流程。 1、客户端发送请求到任意一个…

深度思考:雪花算法snowflake分布式id生成原理详解

雪花算法snowflake是一种优秀的分布式ID生成方案&#xff0c;其优点突出&#xff1a;它能生成全局唯一且递增的ID&#xff0c;确保了数据的一致性和准确性&#xff1b;同时&#xff0c;该算法灵活性强&#xff0c;可自定义各部分bit位&#xff0c;满足不同业务场景的需求&#…

13-API风格(下):RPCAPI介绍

RPC在Go项目开发中用得也非常多&#xff0c;需要我们认真掌握。 RPC介绍 根据维基百科的定义&#xff0c;RPC&#xff08;Remote Procedure Call&#xff09;&#xff0c;即远程过程调用&#xff0c;是一个计算机通信协议。该协议允许运行于一台计算机的程序调用另一台计算机…

【面经八股】搜广推方向:面试记录(十一)

【面经&八股】搜广推方向:面试记录(十一) 文章目录 【面经&八股】搜广推方向:面试记录(十一)1. 自我介绍2. 实习经历问答4. 编程题5. 反问1. 自我介绍 。。。。。。 2. 实习经历问答 就是对自己实习事情要足够的清晰,不熟的不要写在简历上!!! 其中,有个 …

电脑分辨率怎么调,电脑分辨率怎么调整

随着电脑的普及以及网络的发展&#xff0c;我们现在在工作中都离不开对电脑的使用&#xff0c;今天小编教大家设置电脑分辨率&#xff0c;现在我们先了解这个分辨率是什么?通常电脑的显示分辨率就是屏幕分辨率&#xff0c;显示屏大小固定时&#xff0c;显示分辨率越高图像越清…

【Node.js】WebSockets

概述 WebSockets是一种在浏览器和服务器之间建立持久连接的协议&#xff0c;它允许服务器主动推送数据给客户端&#xff0c;并且在客户端和服务器之间实现双向通信。 建立连接&#xff1a;客户端通过在JavaScript代码中使用WebSocket对象来建立WebSockets连接。例如&#xff1…

KUKA机器人KR FORTEC-2 ultra重型机器人介绍

额定负载在480-800kg的KR FORTEC-2 ultra重型机器人专为快速精确处理高惯量的大型部件的工艺而设计&#xff0c;双支撑连接臂使性能更强&#xff0c;并可以在紧凑的空间内保持优异性能。在工业生产中&#xff0c;工件的转动惯量越大&#xff0c;对机器人的有效载荷和臂展要求越…

安全SCDN的威胁情报库对DDOS防护有什么好处

目前网络攻击事件频频发生&#xff0c;DDoS&#xff08;分布式拒绝服务&#xff09;攻击已成为各种企业&#xff08;小到区域性小公司大到各种跨国公司&#xff09;的主要威胁&#xff0c;DDoS 攻击可能会对企业造成重大损害和破坏&#xff0c;比如对目标公司的业务造成产生不利…

探索父进程和子进程

文章目录 通过系统调用查看进程PID父进程、子进程 通过系统调用创建进程-fork初识为什么fork给父进程返回子进程的PID&#xff0c;给子进程返回0fork函数如何做到返回两个值一个变量为什么同时会有两个返回值&#xff1f;bash总结 通过系统调用查看进程PID getpid()函数可以获…

win10配置CLion2022+ubuntu20.04远程部署

背景 在博文ubunut搭建aarch64 cuda交叉编译环境记录中&#xff0c;使用的ubuntu20.04虚拟机安装eclipse来交叉编译aarch64的程序&#xff0c;然后发送到jetson板子上执行。开发一段时间后发现eclipse IDE使用起来不太便捷&#xff0c;因此&#xff0c;考虑使用CLion IDE&…

接口自动化框架搭建(四):pytest的使用

1&#xff0c;使用说明 网上资料比较多&#xff0c;我这边就简单写下 1&#xff0c;目录结构 2&#xff0c;test_1.py创建两条测试用例 def test_1():print(test1)def test_2():print(test2)3&#xff0c;在pycharm中执行 4&#xff0c;执行结果&#xff1a; 2&#xff0…

新能源汽车充电桩常见类型及充电桩站场的智能监管方案

随着新能源汽车市场的迅猛发展&#xff0c;充电桩作为支持其运行的基础设施&#xff0c;也呈现出多样化的类型。这些充电桩不仅在外形和功能上存在差异&#xff0c;更在充电速度、充电方式以及使用场景等方面展现出独特的优势。 一、充电桩类型及区别 1、慢充桩&#xff08;交…

《算法笔记》系列----质数的判断(埃氏筛法)

目录 一、朴素算法 二、埃氏筛法 1、与朴素算法对比 2、算法介绍 3、例题即代码实现 一、朴素算法 从素数的定义中可以知道&#xff0c;一个整数n要被判断为素数&#xff0c;需要判断n是否能被2.3.n- 1中的一个整除。只2&#xff0c;3..n- 1都不能整除n&#xff0c;n才能…