- Java锁的逻辑(结合对象头和ObjectMonitor)
- 还在用饼状图?来瞧瞧这些炫酷的百分比可视化新图形(附代码实现)⛵
- 自动注册实体类到EntityFrameworkCore上下文,并适配ABP及ABPVNext
- 基于Sklearn机器学习代码实战
在JDBC中,executeBatch这个方法可以将多条dml语句批量执行,效率比单条执行executeUpdate高很多,这是什么原理呢?在mysql和oracle中又是如何实现批量执行的呢?本文将给大家介绍这背后的原理.
本实验将通过以下三步进行 a. 记录jdbc在mysql中批量执行和单条执行的耗时 b. 记录jdbc在oracle中批量执行和单条执行的耗时 c. 记录oracle plsql批量执行和单条执行的耗时 相关java和数据库版本如下:Java17,Mysql8,Oracle11G 。
在mysql和oracle中分别创建一张表 。
create table t ( -- mysql中创建表的语句
id int,
name1 varchar(100),
name2 varchar(100),
name3 varchar(100),
name4 varchar(100)
);
create table t ( -- oracle中创建表的语句
id number,
name1 varchar2(100),
name2 varchar2(100),
name3 varchar2(100),
name4 varchar2(100)
);
在实验前需要打开数据库的审计 mysql开启审计:
set global general_log = 1;
oracle开启审计:
alter system set audit_trail=db, extended;
audit insert table by scott; -- 实验采用scott用户批量执行insert的方式
java代码如下:
import java.sql.*;
public class JdbcBatchTest {
/**
* @param dbType 数据库类型,oracle或mysql
* @param totalCnt 插入的总行数
* @param batchCnt 每批次插入的行数,0表示单条插入
*/
public static void exec(String dbType, int totalCnt, int batchCnt) throws SQLException, ClassNotFoundException {
String user = "scott";
String password = "xxxx";
String driver;
String url;
if (dbType.equals("mysql")) {
driver = "com.mysql.cj.jdbc.Driver";
url = "jdbc:mysql://ip/hello?useServerPrepStmts=true&rewriteBatchedStatements=true";
} else {
driver = "oracle.jdbc.OracleDriver";
url = "jdbc:oracle:thin:@ip:orcl";
}
long l1 = System.currentTimeMillis();
Class.forName(driver);
Connection connection = DriverManager.getConnection(url, user, password);
connection.setAutoCommit(false);
String sql = "insert into t values (?, ?, ?, ?, ?)";
PreparedStatement preparedStatement = connection.prepareStatement(sql);
for (int i = 1; i <= totalCnt; i++) {
preparedStatement.setInt(1, i);
preparedStatement.setString(2, "red" + i);
preparedStatement.setString(3, "yel" + i);
preparedStatement.setString(4, "bal" + i);
preparedStatement.setString(5, "pin" + i);
if (batchCnt > 0) {
// 批量执行
preparedStatement.addBatch();
if (i % batchCnt == 0) {
preparedStatement.executeBatch();
} else if (i == totalCnt) {
preparedStatement.executeBatch();
}
} else {
// 单条执行
preparedStatement.executeUpdate();
}
}
connection.commit();
connection.close();
long l2 = System.currentTimeMillis();
System.out.println("总条数:" + totalCnt + (batchCnt>0? (",每批插入:"+batchCnt) : ",单条插入") + ",一共耗时:"+ (l2-l1) + " 毫秒");
}
public static void main(String[] args) throws SQLException, ClassNotFoundException {
exec("mysql", 10000, 50);
}
}
代码中几个注意的点, 。
exec("mysql", 10000, batchCnt);
代入不同的batchCnt值看执行时长 batchCnt=50 总条数:10000,每批插入:50,一共耗时:4369 毫秒 batchCnt=100 总条数:10000,每批插入:100,一共耗时:2598 毫秒 batchCnt=200 总条数:10000,每批插入:200,一共耗时:2211 毫秒 batchCnt=1000 总条数:10000,每批插入:1000,一共耗时:2099 毫秒 batchCnt=10000 总条数:10000,每批插入:10000,一共耗时:2418 毫秒 batchCnt=0 总条数:10000,单条插入,一共耗时:59620 毫秒 。
查看general log batchCnt=50 。
batchCnt=0 。
可以得出几个结论
分析原因: 当执行一条sql语句的时候,客户端发送sql文本到数据库服务器,数据库执行sql再将结果返回给客户端。总耗时 = 数据库执行时间 + 网络传输时间。使用批量执行减少往返的次数,即降低了网络传输时间,总时间因此降低。但是当batchCnt变大,网络传输时间并不是最主要耗时的时候,总时间降低就不会那么明显。特别是当batchCnt=10000,即一次性把1万条语句全部执行完,时间反而变多了,这可能是由于程序和数据库在准备这些入参时需要申请更大的内存,所以耗时更多(我猜的)。 再来说一句,batchCnt这个值是不是能无限大呢,假设我需要插入的是1亿条,那么我能一次性批量插入1亿条吗?当然不行,我们不考虑undo的空间问题,首先你电脑就没有这么大的内存一次性把这1亿条sql的入参全部保存下来,其次mysql还有个参数max_allowed_packet限制单条语句的长度,最大为1G字节。当语句过长的时候就会报"Packet for query is too large (1,773,901 > 1,599,488). You can change this value on the server by setting the 'max_allowed_packet' variable".
exec("oracle", 10000, batchCnt);
代入不同的batchCnt值看执行时长 batchCnt=50 总条数:10000,每批插入:50,一共耗时:2055 毫秒 batchCnt=100 总条数:10000,每批插入:100,一共耗时:1324 毫秒 batchCnt=200 总条数:10000,每批插入:200,一共耗时:856 毫秒 batchCnt=1000 总条数:10000,每批插入:1000,一共耗时:785 毫秒 batchCnt=10000 总条数:10000,每批插入:10000,一共耗时:804 毫秒 batchCnt=0 总条数:10000,单条插入,一共耗时:60830 毫秒 可以看到oracle中执行的效果跟mysql中基本一致,批量执行的效率相比单条执行都大大提升。问题就来了,oracle中并没有这种insert xx values(),()..语法呀,那它是怎么做到批量执行的呢?
查看当执行batchCnt=50的审计视图dba_audit_trail 从审计的结果中可以看到,batchCnt=50的时候,审计记录只有200条(扣除登入和登出),也就是sql只执行了200次。sql_text没有发生改写,仍然是"insert into t values (:1 , :2 , :3 , :4 , :5 )",而且sql_bind只记录了批量执行的最后一个参数,即50的倍数。从awr报告中也能看出的确是只执行了200次(限于篇幅,awr截图省略)。那么oracle是怎么做到只执行200次但插入1万条记录的呢?我们来看看oracle中使用存储过程的批量插入.
准备数据: 首先将t表清空 truncate table t; 用java往t表灌10万数据 exec("oracle", 100000, 1000); 创建t1表 create table t1 as select * from t where 1 = 0,
以下两个procudure的目的相同,都是将t表的数据灌到t1表中。nobatch是单次执行,usebatch是批量执行.
create or replace procedure nobatch is
begin
for x in (select * from t)
loop
insert into t1 (id, name1, name2, name3, name4)
values (x.id, x.name1, x.name2, x.name3, x.name4);
end loop;
commit;
end nobatch;
/
create or replace procedure usebatch (p_array_size in pls_integer)
is
type array is table of t%rowtype;
l_data array;
cursor c is select * from t;
begin
open c;
loop
fetch c bulk collect into l_data limit p_array_size;
forall i in 1..l_data.count insert into t1 values l_data(i);
exit when c%notfound;
end loop;
commit;
close c;
end usebatch;
/
执行上述存储过程 。
SQL> exec nobatch;
Elapsed: 00:00:32.92
SQL> exec usebatch(50);
Elapsed: 00:00:00.77
SQL> exec usebatch(100);
Elapsed: 00:00:00.47
SQL> exec usebatch(1000);
Elapsed: 00:00:00.19
SQL> exec usebatch(100000);
Elapsed: 00:00:00.26
存储过程批量执行效率也远远高于单条执行。查看usebatch(50)执行时的审计日志,sql_bind也只记录了批量执行的最后一个参数,即50的倍数。跟前面jdbc使用executeBatch批量执行时的记录内容一样。由此可知jdbc的executeBatch跟存储过程的批量执行应该是采用的同样的方法.
存储过程的这个关键点就是forall。查阅相关文档。 The FORALL statement runs one DML statement multiple times, with different values in the VALUES and WHERE clauses. The different values come from existing, populated collections or host arrays. The FORALL statement is usually much faster than an equivalent FOR LOOP statement. The FORALL syntax allows us to bind the contents of a collection to a single DML statement, allowing the DML to be run for each row in the collection without requiring a context switch each time. 翻译过来就是forall很快,原因就是不需要每次执行的时候等待参数.
参考: https://docs.oracle.com/en/database/oracle/oracle-database/19/lnpls/FORALL-statement.html#GUID-C45B8241-F9DF-4C93-8577-C840A25963DB https://oracle-base.com/articles/9i/bulk-binds-and-record-processing-9i https://www.akadia.com/services/ora_bulk_insert.html 。
最后此篇关于Java-JDBC批量插入原理的文章就讲到这里了,如果你想了解更多关于Java-JDBC批量插入原理的内容请搜索CFSDN的文章或继续浏览相关文章,希望大家以后支持我的博客! 。
本文全面深入地探讨了Docker容器通信技术,从基础概念、网络模型、核心组件到实战应用。详细介绍了不同网络模式及其实现,提供了容器通信的技术细节和实用案例,旨在为专业从业者提供深入的技术洞见和实
📒博客首页:崇尚学技术的科班人 🍣今天给大家带来的文章是《Dubbo快速上手 -- 带你了解Dubbo使用、原理》🍣 🍣希望各位小伙伴们能够耐心的读完这篇文章🍣 🙏博主也在学习阶段,如若发
一、写在前面 我们经常使用npm install ,但是你是否思考过它内部的原理是什么? 1、执行npm install 它背后帮助我们完成了什么操作? 2、我们会发现还有一个成为package-lo
Base64 Base64 是什么?是将字节流转换成可打印字符、将可打印字符转换为字节流的一种算法。Base64 使用 64 个可打印字符来表示转换后的数据。 准确的来说,Base64 不算
目录 协程定义 生成器和yield语义 Future类 IOLoop类 coroutine函数装饰器 总结 tornado中的
切片,这是一个在go语言中引入的新的理念。它有一些特征如下: 对数组抽象 数组长度不固定 可追加元素 切片容量可增大 容量大小成片增加 我们先把上面的理念整理在这
文章来源:https://sourl.cn/HpZHvy 引 言 本文主要论述的是“RPC 实现原理”,那么首先明确一个问题什么是 RPC 呢?RPC 是 Remote Procedure Call
源码地址(包含所有与springmvc相关的,静态文件路径设置,request请求入参接受,返回值处理converter设置等等): spring-framework/WebMvcConfigurat
请通过简单的java类向我展示一个依赖注入(inject)原理的小例子虽然我已经了解了spring,但是如果我需要用简单的java类术语来解释它,那么你能通过一个简单的例子向我展示一下吗?提前致谢。
1、背景 我们平常使用手机和电脑上网,需要访问公网上的网络资源,如逛淘宝和刷视频,那么手机和电脑是怎么知道去哪里去拿到这个网络资源来下载到本地的呢? 就比如我去食堂拿吃的,我需要
大家好,我是飞哥! 现在 iptables 这个工具的应用似乎是越来越广了。不仅仅是在传统的防火墙、NAT 等功能出现,在今天流行的的 Docker、Kubernets、Istio 项目中也经
本篇涉及到的所有接口在公开文档中均无,需要下载 GitHub 上的源码,自己创建私有类的文档。 npm run generateDocumentation -- --private yarn gene
我最近在很多代码中注意到人们将硬编码的配置(如端口号等)值放在类/方法的深处,使其难以找到,也无法配置。 这是否违反了 SOLID 原则?如果不是,我是否可以向我的团队成员引用另一个“原则”来说明为什
我是 C#、WPF 和 MVVM 模式的新手。很抱歉这篇很长的帖子,我试图设定我所有的理解点(或不理解点)。 在研究了很多关于 WPF 提供的命令机制和 MVVM 模式的文本之后,我在弄清楚如何使用这
可比较的 jQuery 函数 $.post("/example/handler", {foo: 1, bar: 2}); 将创建一个带有 post 参数 foo=1&bar=2 的请求。鉴于 $htt
如果Django不使用“延迟查询执行”原则,主要问题是什么? q = Entry.objects.filter(headline__startswith="What") q = q.filter(
我今天发现.NET框架在做计算时遵循BODMAS操作顺序。即计算按以下顺序进行: 括号 订单 部门 乘法 添加 减法 但是我四处搜索并找不到任何文档确认 .NET 绝对 遵循此原则,是否有此类文档?如
已结束。此问题不符合 Stack Overflow guidelines .它目前不接受答案。 我们不允许提出有关书籍、工具、软件库等方面的建议的问题。您可以编辑问题,以便用事实和引用来回答它。 关闭
API 回顾 在创建 Viewer 时可以直接指定 影像供给器(ImageryProvider),官方提供了一个非常简单的例子,即离屏例子(搜 offline): new Cesium.Viewer(
As it currently stands, this question is not a good fit for our Q&A format. We expect answers to be
我是一名优秀的程序员,十分优秀!