背景
一个项目,从kafka获取数据后,经过业务处理,生成insert、update、delete语句,在Orcale数据库执行。项目最初采用单条sql语句非预编译提交的方式执行sql语句。随着kafka数据的增多,Oracle执行sql语句的性能变差,最后导致了整个Oracle数据库崩溃,无法执行任何sql语句。因此需要进行优化。最终采用的优化方案是批量提交+SQL预编译解决了问题。下面就探讨一下批量提交和SQL预编译对性能的提升有多大影响。
SQL预编译与批量提交
提到SQL预编译,首先想到的是防止SQL注入。SQL编译对提升sql执行性能有何帮助呢,博主查阅了很多资料,大都使用以下图来说明:
当客户发送一条SQL语句给服务器后,服务器总是需要校验SQL语句的语法格式是否正确,然后把SQL语句编译成可执行的函数,最后才是执行SQL语句。其中校验语法,和编译所花的时间可能比执行SQL语句花的时间还要多。
在Oracle数据库和Mysql数据库中,实现SQL预编译的机制是不同的,下面通过实验,比较SQL预编译对两个数据库SQL执行的性能提升有何差异。
在jdbc中的批量提交中,预编译(PrepareStatement)必须是相同结构的sql语句(sql语句完全相同,参数不同),才可以批量提交。不同结构的sql语句不能在同一批次中提交。非预编译提交(Statement)可以批量提交结构不同的sql语句。
基于上述机制,预编译sql只能相同语句的sql才能批量提交,如果相同结构的sql语句很少,大部分是结构不同的sql语句,那么是批量提交预编译性能高,还是批量提交非预编译性能高,该如何抉择呢?通过下面的实验进行解疑。
Oracle数据实验
采用原生jdbc连接oracle数据库,进行非预编译单条提交、预编译单条提交、非预编译批量提交、预编译批量提交进行比较。
测试表结构如下:
insert语句
非预编译单条提交
代码如下:
/**
* 非预编译,单条提交,提交5000次,insert
* @throws Exception
*/
public static void test1_1() throws Exception{
Class.forName("oracle.jdbc.driver.OracleDriver");
Connection com = DriverManager.getConnection("jdbc:oracle:thin:@localhost:1521/orcl?useUnicode=true&characterEncoding=utf8", "demo", "AsDf!123");
// DriverManager 注册驱动
// Connection 数据库连接对象 url(指定连接的路径 语法:“jdbc:mysql://ip地址:端口号/数据库名称”)
Statement stat = com.createStatement();
//执行 sql 语句的对象
long start=System.currentTimeMillis();
for(int i=0;i<10000;i++){
String sql="";
sql="INSERT INTO ceshi (\n" +
"\t name,\n" +
"\t age,\n" +
"\t sex,\n" +
"\t school,\n" +
"\t birth,\n" +
"\t money,\n" +
"\t createtime,\n" +
"\t remark \n" +
")\n" +
"VALUES\n" +
"\t(\n" +
"\t\t '张三',\n" +
"\t\t '18',\n" +
"\t\t '男',\n" +
"\t\t '测试测试',\n" +
"\t\t to_date('2023/03/11','yyyy-MM-dd'),\n" +
"\t\t '10.22',\n" +
"\t\t to_date('2023-11-11 11:11:11','yyyy-mm-dd HH:MI:SS'),\n" +
"\t\t'测试11111111测试222222222测试'\n" +
"\t)";
boolean result = stat.execute(sql);
}
System.out.println("test1_1执行时间:"+(System.currentTimeMillis()-start));
com.close();
stat.close();
com.close();
}
insert5000条数据,执行三次,结果如下:
test1_1执行时间:806
test1_1执行时间:749
test1_1执行时间:762
预编译单条提交
代码如下:
/**
* 预编译,单条提交,提交5000次,insert
*/
public static void test1_2() throws Exception{
Class.forName("oracle.jdbc.driver.OracleDriver");
Connection com = DriverManager.getConnection("jdbc:oracle:thin:@localhost:1521/orcl?useUnicode=true&characterEncoding=utf8", "demo", "AsDf!123");
// DriverManager 注册驱动
// Connection 数据库连接对象 url(指定连接的路径 语法:“jdbc:mysql://ip地址:端口号/数据库名称”)
//执行 sql 语句的对象
String sql="INSERT INTO ceshi (\n" +
"\t name,\n" +
"\t age,\n" +
"\t sex,\n" +
"\t school,\n" +
"\t birth,\n" +
"\t money,\n" +
"\t createtime,\n" +
"\t remark \n" +
")\n" +
"VALUES\n" +
"\t(\n" +
"\t\t ?,\n" +
"\t\t ?,\n" +
"\t\t ?,\n" +
"\t\t ?,\n" +
"\t\t ?,\n" +
"\t\t ?,\n" +
"\t\t ?,\n" +
"\t\t ? \n" +
"\t)";
PreparedStatement stat = com.prepareStatement(sql);
long start=System.currentTimeMillis();
for(int i=0;i<10000;i++){
Date date = new Date(System.currentTimeMillis());
Timestamp tt = new Timestamp(date.getTime());
stat.setObject(1,"张三"+i);
stat.setObject(2,20);
stat.setObject(3,"男"+i);
stat.setObject(4,"测试ceshi"+i);
//stat.setObject(5,"to_date('2023/03/11','yyyy-MM-dd')");
stat.setObject(5, date);
stat.setObject(6,10);
// stat.setObject(7,"TO_TIMESTAMP('2023-11-11 11:11:11','yyyy-mm-dd HH:MI:SS')");
//stat.setObject(7,Calendar.getInstance().getTime());
stat.setObject(7,tt);
stat.setObject(8,"测试11111111测试222222222测试"+i);
boolean result = stat.execute();
}
System.out.println("test1_2执行时间:"+(System.currentTimeMillis()-start));
com.close();
stat.close();
com.close();
}
insert5000条数据,结果如下:
test1_2执行时间:771
test1_2执行时间:787
test1_2执行时间:780
非预编译批量提交
代码如下:
/**
* 非预编译批量提交 insert 5000条
*/
public static void test2_1() throws Exception{
Class.forName("oracle.jdbc.driver.OracleDriver");
Connection com = DriverManager.getConnection("jdbc:oracle:thin:@localhost:1521/orcl?useUnicode=true&characterEncoding=utf8", "demo", "AsDf!123");
// DriverManager 注册驱动
// Connection 数据库连接对象 url(指定连接的路径 语法:“jdbc:mysql://ip地址:端口号/数据库名称”)
Statement stat = com.createStatement();
//执行 sql 语句的对象
for(int i=0;i<10000;i++){
String sql="";
sql="INSERT INTO ceshi (\n" +
"\t name,\n" +
"\t age,\n" +
"\t sex,\n" +
"\t school,\n" +
"\t birth,\n" +
"\t money,\n" +
"\t createtime,\n" +
"\t remark \n" +
")\n" +
"VALUES\n" +
"\t(\n" +
"\t\t '张三',\n" +
"\t\t '18',\n" +
"\t\t '男',\n" +
"\t\t '测试测试',\n" +
"\t\t to_date('2023/03/11','yyyy-MM-dd'),\n" +
"\t\t '10.22',\n" +
"\t\t to_date('2023-11-11 11:11:11','yyyy-mm-dd HH:MI:SS'),\n" +
"\t\t'测试11111111测试222222222测试'\n" +
"\t)";
stat.addBatch(sql);
}
long start=System.currentTimeMillis();
stat.executeBatch();
System.out.println("test2_1执行时间:"+(System.currentTimeMillis()-start));
com.close();
stat.close();
com.close();
}
一次性批量提交5000条,结果如下:
test2_1执行时间:779
test2_1执行时间:834
test2_1执行时间:823
预编译批量提交
代码如下:
/**
* 预编译批量提交 insert 5000条
*/
public static void test2_2() throws Exception{
Class.forName("oracle.jdbc.driver.OracleDriver");
Connection com = DriverManager.getConnection("jdbc:oracle:thin:@localhost:1521/orcl?useUnicode=true&characterEncoding=utf8", "demo", "AsDf!123");
// DriverManager 注册驱动
// Connection 数据库连接对象 url(指定连接的路径 语法:“jdbc:mysql://ip地址:端口号/数据库名称”)
//执行 sql 语句的对象
String sql="INSERT INTO ceshi (\n" +
"\t name,\n" +
"\t age,\n" +
"\t sex,\n" +
"\t school,\n" +
"\t birth,\n" +
"\t money,\n" +
"\t createtime,\n" +
"\t remark \n" +
")\n" +
"VALUES\n" +
"\t(\n" +
"\t\t ?,\n" +
"\t\t ?,\n" +
"\t\t ?,\n" +
"\t\t ?,\n" +
"\t\t ?,\n" +
"\t\t ?,\n" +
"\t\t ?,\n" +
"\t\t ? \n" +
"\t)";
PreparedStatement stat = com.prepareStatement(sql);
for(int i=0;i<10000;i++){
Date date = new Date(System.currentTimeMillis());
Timestamp tt = new Timestamp(date.getTime());
stat.setObject(1,"张三"+i);
stat.setObject(2,20);
stat.setObject(3,"男"+i);
stat.setObject(4,"测试ceshi"+i);
//stat.setObject(5,"to_date('2023/03/11','yyyy-MM-dd')");
stat.setObject(5, date);
stat.setObject(6,10);
// stat.setObject(7,"TO_TIMESTAMP('2023-11-11 11:11:11','yyyy-mm-dd HH:MI:SS')");
//stat.setObject(7,Calendar.getInstance().getTime());
stat.setObject(7,tt);
stat.setObject(8,"测试11111111测试222222222测试"+i);
stat.addBatch();
}
long start=System.currentTimeMillis();
stat.executeBatch();
System.out.println("test2_2执行时间:"+(System.currentTimeMillis()-start));
com.close();
stat.close();
com.close();
}
一次性提交5000条,执行结果如下:
test2_2执行时间:34
test2_2执行时间:59
test2_2执行时间:36
insert…values(),()插入多条语句
在oracle中,一个insert插入多条值的sql语法为:
INSERT ALL
INTO ceshi(name,age,sex,school,birth,money,createtime,remark) values('测试1',19,'女','xuexiao',to_date('2023/03/11','yyyy-MM-dd'),2023,to_date('2023-11-11 11:11:11','yyyy-mm-dd HH:MI:SS'),'测试111')
INTO ceshi(name,age,sex,school,birth,money,createtime,remark) values('测试2',19,'女','xuexiao',to_date('2023/03/11','yyyy-MM-dd'),2023,to_date('2023-11-11 11:11:11','yyyy-mm-dd HH:MI:SS'),'测试111')
INTO ceshi(name,age,sex,school,birth,money,createtime,remark) values('测试3',19,'女','xuexiao',to_date('2023/03/11','yyyy-MM-dd'),2023,to_date('2023-11-11 11:11:11','yyyy-mm-dd HH:MI:SS'),'测试111')
SELECT 1 FROM DUAL;
测试该语句一次性插入1000条数据,代码如下:
public static void test3_1() throws Exception{
Class.forName("oracle.jdbc.driver.OracleDriver");
Connection com = DriverManager.getConnection("jdbc:oracle:thin:@localhost:1521/orcl?useUnicode=true&characterEncoding=utf8", "demo", "AsDf!123");
// DriverManager 注册驱动
// Connection 数据库连接对象 url(指定连接的路径 语法:“jdbc:mysql://ip地址:端口号/数据库名称”)
Statement stat = com.createStatement();
//执行 sql 语句的对象
String sql="INSERT ALL ";
for(int i=0;i<1000;i++){
sql+=" INTO ceshi (\n" +
"\t name,\n" +
"\t age,\n" +
"\t sex,\n" +
"\t school,\n" +
"\t birth,\n" +
"\t money,\n" +
"\t createtime,\n" +
"\t remark \n" +
")\n" +
"VALUES\n" +
"\t(\n" +
"\t\t '张三',\n" +
"\t\t '18',\n" +
"\t\t '男',\n" +
"\t\t '测试测试',\n" +
"\t\t to_date('2023/03/11','yyyy-MM-dd'),\n" +
"\t\t '10.22',\n" +
"\t\t to_date('2023-11-11 11:11:11','yyyy-mm-dd HH:MI:SS'),\n" +
"\t\t'测试11111111测试222222222测试'\n" +
"\t) ";
}
sql+=" SELECT 1 FROM DUAL";
long start=System.currentTimeMillis();
stat.execute(sql);
System.out.println("test3_1执行时间:"+(System.currentTimeMillis()-start));
com.close();
stat.close();
com.close();
}
执行结果如下:
test3_1执行时间:1748
结论
把上面的结果汇总来看,如下:
非预编译,单条提交,提交5000次
test1_1执行时间:806
test1_1执行时间:749
test1_1执行时间:762预编译,单条提交,提交5000次
test1_2执行时间:771
test1_2执行时间:787
test1_2执行时间:780非预编译批量提交 insert 5000条
test2_1执行时间:779
test2_1执行时间:834
test2_1执行时间:823
预编译批量提交 insert 5000条
test2_2执行时间:34
test2_2执行时间:59
test2_2执行时间:36
insert…values语句一次性插入1000条
test3_1执行时间:1748
从上面结果可知,insert…values…多条值的情况,性能最差。非预编译单条提交、预编译单条提交、非预编译批量提交性能都在一个量级。预编译批量提交性能最快。因此,Oracle数据库大批量插入数据时,采用预编译批量提交,性能最快。
update语句
非预编译单条提交
代码如下:
/**
* 非预编译单条update提交,5000条
*/
public static void test4_1() throws Exception{
Class.forName("oracle.jdbc.driver.OracleDriver");
Connection com = DriverManager.getConnection("jdbc:oracle:thin:@localhost:1521/orcl?useUnicode=true&characterEncoding=utf8", "demo", "AsDf!123");
// DriverManager 注册驱动
// Connection 数据库连接对象 url(指定连接的路径 语法:“jdbc:mysql://ip地址:端口号/数据库名称”)
Statement stat = com.createStatement();
Random random=new Random();
//执行 sql 语句的对象
long start=System.currentTimeMillis();
for(int i=0;i<10000;i++){
String sql="";
sql="update ceshi set age=11"+i+",sex='未知"+i+"',school='修改"+i+"',birth=to_date('2023/03/11','yyyy-MM-dd') where id = "+(35000+i);//*random.nextInt(30000)
// System.out.println(sql);
boolean result = stat.execute(sql);
}
System.out.println("test4_1执行时间:"+(System.currentTimeMillis()-start));
com.close();
stat.close();
com.close();
}
执行5000条,结果如下:
test4_1执行时间:14881
执行10000条,结果如下:
test4_1执行时间:32295
预编译单条提交
代码如下:
/**
* 预编译单条update提交,5000条
* @throws Exception
*/
public static void test4_2() throws Exception{
Class.forName("oracle.jdbc.driver.OracleDriver");
Connection com = DriverManager.getConnection("jdbc:oracle:thin:@localhost:1521/orcl?useUnicode=true&characterEncoding=utf8", "demo", "AsDf!123");
// DriverManager 注册驱动
// Connection 数据库连接对象 url(指定连接的路径 语法:“jdbc:mysql://ip地址:端口号/数据库名称”)
//执行 sql 语句的对象
String sql="update ceshi set age=?,sex=?,school=?,birth=? where id = ?";
PreparedStatement stat = com.prepareStatement(sql);
long start=System.currentTimeMillis();
for(int i=0;i<10000;i++){
Date date = new Date(System.currentTimeMillis());
Timestamp tt = new Timestamp(date.getTime());
stat.setObject(1,i);
stat.setObject(2,"男"+i);
stat.setObject(3,"测试ceshi"+i);
//stat.setObject(5,"to_date('2023/03/11','yyyy-MM-dd')");
stat.setObject(4, date);
stat.setObject(5,15000+i);
// stat.setObject(7,"TO_TIMESTAMP('2023-11-11 11:11:11','yyyy-mm-dd HH:MI:SS')");
//stat.setObject(7,Calendar.getInstance().getTime());
boolean result = stat.execute();
}
System.out.println("test4_2执行时间:"+(System.currentTimeMillis()-start));
com.close();
stat.close();
com.close();
}
执行5000条,结果如下:
test4_2执行时间:11430
执行10000条,结果如下:
test4_2执行时间:25123
非预编译批量提交
代码如下:
/**
* 非预编译,批量提交5000条
*/
public static void test5_1() throws Exception{
Class.forName("oracle.jdbc.driver.OracleDriver");
Connection com = DriverManager.getConnection("jdbc:oracle:thin:@localhost:1521/orcl?useUnicode=true&characterEncoding=utf8", "demo", "AsDf!123");
// DriverManager 注册驱动
// Connection 数据库连接对象 url(指定连接的路径 语法:“jdbc:mysql://ip地址:端口号/数据库名称”)
Statement stat = com.createStatement();
//执行 sql 语句的对象
for(int i=0;i<10000;i++){
String sql="update ceshi set age=11"+i+",sex='未知"+i+"',school='修改"+i+"',birth=to_date('2023/03/11','yyyy-MM-dd') where id = "+(50000+i);//*random.nextInt(30000)
stat.addBatch(sql);
}
long start=System.currentTimeMillis();
stat.executeBatch();
System.out.println("test5_1执行时间:"+(System.currentTimeMillis()-start));
com.close();
stat.close();
com.close();
}
执行5000条,结果如下:
test5_1执行时间:15733
执行10000条,结果如下:
test5_1执行时间:31539
预编译批量提交
代码如下:
/**
* 预编译批量提交5000条
*/
public static void test5_2() throws Exception{
Class.forName("oracle.jdbc.driver.OracleDriver");
Connection com = DriverManager.getConnection("jdbc:oracle:thin:@localhost:1521/orcl?useUnicode=true&characterEncoding=utf8", "demo", "AsDf!123");
// DriverManager 注册驱动
// Connection 数据库连接对象 url(指定连接的路径 语法:“jdbc:mysql://ip地址:端口号/数据库名称”)
//执行 sql 语句的对象
String sql="update ceshi set age=?,sex=?,school=?,birth=? where id = ?";
PreparedStatement stat = com.prepareStatement(sql);
for(int i=0;i<10000;i++){
Date date = new Date(System.currentTimeMillis());
Timestamp tt = new Timestamp(date.getTime());
stat.setObject(1,i);
stat.setObject(2,"男"+i);
stat.setObject(3,"测试ceshi"+i);
//stat.setObject(5,"to_date('2023/03/11','yyyy-MM-dd')");
stat.setObject(4, date);
stat.setObject(5,30000+i);
// stat.setObject(7,"TO_TIMESTAMP('2023-11-11 11:11:11','yyyy-mm-dd HH:MI:SS')");
//stat.setObject(7,Calendar.getInstance().getTime());
stat.addBatch();
}
long start=System.currentTimeMillis();
stat.executeBatch();
System.out.println("test5_2执行时间:"+(System.currentTimeMillis()-start));
com.close();
stat.close();
com.close();
}
执行5000条,结果如下:
test5_2执行时间:8808
执行10000条,结果如下:
test5_2执行时间:17672
结论
汇总update实验结果如下:
非预编译单条update提交,5000条:
test4_1执行时间:14881
非预编译单条update提交,10000条:
test4_1执行时间:32295
预编译单条update提交,5000条:
test4_2执行时间:11430
预编译单条update提交,10000条:
test4_2执行时间:25123
非预编译批量提交update ,5000条:
test5_1执行时间:15733
非预编译批量提交update ,10000条:
test5_1执行时间:31539
预编译批量提交update ,5000条:
test5_2执行时间:8808
预编译批量提交update ,10000条:
test5_2执行时间:17672
由上面结果可知,预编译单条提交性能高于非预编译单条提交。这证明预编译确实减少了重复sql解析、校验的时间,提高了性能。但是整体而言,预编译对非预编译性能的提升效果不是特别的明显。
非预编译批量提交性能与非批量提交单条提交性能基本一致,这就说明批量提交对性能的提升没有太大明显。
预编译批量提交性能明显优于非预编译单条提交和非预编译批量提交,性能快了一倍。由此可见在Oracle中,预编译对性能的提升很大。在预编译情况下,批量提交性能比单条提交性能的提升还是有些明显的。
因此,Oracle数据库在update情况下,首要影响性能的是SQL预编译,第二因素是批量提交。而且update语句的执行性能,要比insert低很多。
delete语句
代码如下:
/**
* 非预编译单条delete,5000条
*/
public static void test6_1() throws Exception{
Class.forName("oracle.jdbc.driver.OracleDriver");
Connection com = DriverManager.getConnection("jdbc:oracle:thin:@localhost:1521/orcl?useUnicode=true&characterEncoding=utf8", "demo", "AsDf!123");
// DriverManager 注册驱动
// Connection 数据库连接对象 url(指定连接的路径 语法:“jdbc:mysql://ip地址:端口号/数据库名称”)
Statement stat = com.createStatement();
//执行 sql 语句的对象
long start=System.currentTimeMillis();
for(int i=0;i<5000;i++){
String sql="";
sql="delete from ceshi where id="+i;
boolean result = stat.execute(sql);
}
System.out.println("test6_1执行时间:"+(System.currentTimeMillis()-start));
com.close();
stat.close();
com.close();
}
/**
* 预编译单条delet,5000条
*/
public static void test6_2() throws Exception{
Class.forName("oracle.jdbc.driver.OracleDriver");
Connection com = DriverManager.getConnection("jdbc:oracle:thin:@localhost:1521/orcl?useUnicode=true&characterEncoding=utf8", "demo", "AsDf!123");
// DriverManager 注册驱动
// Connection 数据库连接对象 url(指定连接的路径 语法:“jdbc:mysql://ip地址:端口号/数据库名称”)
//执行 sql 语句的对象
String sql="delete from ceshi where id = ?";
PreparedStatement stat = com.prepareStatement(sql);
long start=System.currentTimeMillis();
for(int i=0;i<5000;i++){
Date date = new Date(System.currentTimeMillis());
Timestamp tt = new Timestamp(date.getTime());
stat.setObject(1,i+10000);
boolean result = stat.execute();
}
System.out.println("test6_2执行时间:"+(System.currentTimeMillis()-start));
com.close();
stat.close();
com.close();
}
/**
* 非预编译批量提交,5000条
*/
public static void test7_1() throws Exception{
Class.forName("oracle.jdbc.driver.OracleDriver");
Connection com = DriverManager.getConnection("jdbc:oracle:thin:@localhost:1521/orcl?useUnicode=true&characterEncoding=utf8", "demo", "AsDf!123");
// DriverManager 注册驱动
// Connection 数据库连接对象 url(指定连接的路径 语法:“jdbc:mysql://ip地址:端口号/数据库名称”)
Statement stat = com.createStatement();
//执行 sql 语句的对象
for(int i=0;i<5000;i++){
String sql= sql="delete from ceshi where id="+(i+20000);
stat.addBatch(sql);
}
long start=System.currentTimeMillis();
stat.executeBatch();
System.out.println("test7_1执行时间:"+(System.currentTimeMillis()-start));
com.close();
stat.close();
com.close();
}
/**
* 预编译批量提交,5000条
*/
public static void test7_2() throws Exception{
Class.forName("oracle.jdbc.driver.OracleDriver");
Connection com = DriverManager.getConnection("jdbc:oracle:thin:@localhost:1521/orcl?useUnicode=true&characterEncoding=utf8", "demo", "AsDf!123");
// DriverManager 注册驱动
// Connection 数据库连接对象 url(指定连接的路径 语法:“jdbc:mysql://ip地址:端口号/数据库名称”)
//执行 sql 语句的对象
String sql="delete from ceshi where id = ?";
PreparedStatement stat = com.prepareStatement(sql);
for(int i=0;i<5000;i++){
Date date = new Date(System.currentTimeMillis());
Timestamp tt = new Timestamp(date.getTime());
stat.setObject(1,i+5000);
stat.addBatch();
}
long start=System.currentTimeMillis();
stat.executeBatch();
System.out.println("test7_2执行时间:"+(System.currentTimeMillis()-start));
com.close();
stat.close();
com.close();
}
执行结果如下:
delete语句:
非预编译单条delete,5000条:
test6_1执行时间:15559
预编译单条delete,5000条:
test6_2执行时间:12477
非预编译批量提交 5000条:
预编译批量提交,5000条:
test7_1执行时间:15599
预编译批量提交,5000条:
test7_2执行时间:7433
结论
由上面实验数据可知,delete语句与update语句情况类似,非预编译单条和批量性能一样。预编译性能提升明显,且预编译批量提交性能更佳。
Oracle总体结论
对于Oracle数据库而言,执行insert语句比update语句和delete语句性能要快。且预编译SQL对sql执行性能提升有效果。对于批量提交而言,非预编译批量提交和单条提交性能差距不大。预编译批量提交比预编译单条提交性能提升较为明显。所以,要提升Oracle数据库sql的执行性能,采用预编译+批量提交,是最好的优化方案。
MYSQL实验数据
下面看MYSQL数据库的批量提交和预编译SQL,结论是否与Oracle数据库一致呢。需要特别注意的是,在MYSQL中,预编译和批量提交功能默认是关闭的,要在jdbc连接数据库的url上,加以下参数,才能开启预编译功能和批量提交功能:
rewriteBatchedStatements=true 开启批量提交
useServerPrepStmts=true 开启SQL预编译
cachePrepStmts=true 开启预编译缓存
以上三个参数这里不过多解释,本篇主要比较SQL执行性能。
insert 语句
预编译/非预编译单条提交
代码如下:
public static void test1_1() throws Exception{
Class.forName("com.mysql.cj.jdbc.Driver");
Connection com = DriverManager.getConnection("jdbc:mysql://localhost:3306/demo?useServerPrepStmts=true&cachePrepStmts=true", "root", "AsDf!123");
// DriverManager 注册驱动
// Connection 数据库连接对象 url(指定连接的路径 语法:“jdbc:mysql://ip地址:端口号/数据库名称”)
Statement stat = com.createStatement();
//执行 sql 语句的对象
long start=System.currentTimeMillis();
for(int i=0;i<5000;i++){
String sql="";
sql="INSERT INTO `demo`.`ceshi` (\n" +
"\t`name`,\n" +
"\t`age`,\n" +
"\t`sex`,\n" +
"\t`school`,\n" +
"\t`birth`,\n" +
"\t`money`,\n" +
"\t`createtime`,\n" +
"\t`remark`\n" +
")\n" +
"VALUES\n" +
"\t(\n" +
"\t\t'张三',\n" +
"\t\t'18',\n" +
"\t\t'男',\n" +
"\t\t'测试测试',\n" +
"\t\t'2023-03-07',\n" +
"\t\t'10.22',\n" +
"\t\t'2023-03-07 23:05:11',\n" +
"\t\t'测试11111111测试222222222测试'\n" +
"\t)";
boolean result = stat.execute(sql);
}
System.out.println("test1_1执行时间:"+(System.currentTimeMillis()-start));
com.close();
stat.close();
com.close();
}
public static void test1_2() throws Exception{//
Class.forName("com.mysql.cj.jdbc.Driver");
Connection com = DriverManager.getConnection("jdbc:mysql://localhost:3306/demo?useServerPrepStmts=true&cachePrepStmts=true", "root", "AsDf!123");
// DriverManager 注册驱动
// Connection 数据库连接对象 url(指定连接的路径 语法:“jdbc:mysql://ip地址:端口号/数据库名称”)
String sql="INSERT INTO ceshi(`name`,`age`,`sex`,`school`,`birth`,`money`,`createtime`,`remark`) VALUES(?,?,?,?,?,?,?,?)";
System.out.println(sql);
PreparedStatement stat = com.prepareStatement(sql);
//执行 sql 语句的对象
long start=System.currentTimeMillis();
for(int i=0;i<5000;i++){
stat.setObject(1,"张三"+i);
stat.setObject(2,"20"+i);
stat.setObject(3,"男"+i);
stat.setObject(4,"测试ceshi"+i);
stat.setObject(5,"2023-03-07");
stat.setObject(6,"10.22");
stat.setObject(7,"2023-03-07 23:05:11");
stat.setObject(8,"测试11111111测试222222222测试"+i);
boolean result = stat.execute();
}
System.out.println("test1_2执行时间:"+(System.currentTimeMillis()-start));
com.close();
stat.close();
com.close();
}
执行结果如下:
非预编译,单条提交,提交5000次,insert:
test1_1执行时间:12177
test1_1执行时间:10684
非预编译,单条提交,提交10000次,insert:
test1_1执行时间:21078
test1_1执行时间:21581
预编译,单条提交,提交5000次,insert
test1_2执行时间:10660
test1_2执行时间:10885
预编译,单条提交,提交10000次,insert
test1_2执行时间:21487
test1_2执行时间:21710
结论:对MYSQL的insert而言,单条提交sql,预编译和非预编译性能一样差。
预编译/非预编译批量提交
代码如下:
public static void test2_1() throws Exception{
Class.forName("com.mysql.cj.jdbc.Driver");
Connection com = DriverManager.getConnection("jdbc:mysql://localhost:3306/demo?rewriteBatchedStatements=true", "root", "AsDf!123");
// DriverManager 注册驱动
// Connection 数据库连接对象 url(指定连接的路径 语法:“jdbc:mysql://ip地址:端口号/数据库名称”)
Statement stat = com.createStatement();
//执行 sql 语句的对象
for(int i=0;i<10000;i++){
String sql="";
sql="INSERT INTO `demo`.`ceshi` (\n" +
"\t`name`,\n" +
"\t`age`,\n" +
"\t`sex`,\n" +
"\t`school`,\n" +
"\t`birth`,\n" +
"\t`money`,\n" +
"\t`createtime`,\n" +
"\t`remark`\n" +
")\n" +
"VALUES\n" +
"\t(\n" +
"\t\t'张三',\n" +
"\t\t'18',\n" +
"\t\t'男',\n" +
"\t\t'测试测试',\n" +
"\t\t'2023-03-07',\n" +
"\t\t'10.22',\n" +
"\t\t'2023-03-07 23:05:11',\n" +
"\t\t'测试11111111测试222222222测试'\n" +
"\t)";
stat.addBatch(sql);
}
long start=System.currentTimeMillis();
stat.executeBatch();
System.out.println("test2_1执行时间:"+(System.currentTimeMillis()-start));
com.close();
stat.close();
com.close();
}
public static void test2_2() throws Exception{//&useServerPrepStmts=true
Class.forName("com.mysql.cj.jdbc.Driver");
Connection com = DriverManager.getConnection("jdbc:mysql://localhost:3306/demo?rewriteBatchedStatements=true&useServerPrepStmts=true", "root", "AsDf!123");
// DriverManager 注册驱动
// Connection 数据库连接对象 url(指定连接的路径 语法:“jdbc:mysql://ip地址:端口号/数据库名称”)
String sql="INSERT INTO ceshi(`name`,`age`,`sex`,`school`,`birth`,`money`,`createtime`,`remark`) VALUES(?,?,?,?,?,?,?,?)";
System.out.println(sql);
PreparedStatement stat = com.prepareStatement(sql);
//执行 sql 语句的对象
for(int i=0;i<5000;i++){
stat.setObject(1,"张三"+i);
stat.setObject(2,"20"+i);
stat.setObject(3,"男"+i);
stat.setObject(4,"测试ceshi"+i);
stat.setObject(5,"2023-03-07");
stat.setObject(6,"10.22");
stat.setObject(7,"2023-03-07 23:05:11");
stat.setObject(8,"测试11111111测试222222222测试"+i);
stat.addBatch();
}
long start=System.currentTimeMillis();
stat.executeBatch();
System.out.println("test2_2执行时间:"+(System.currentTimeMillis()-start));
com.close();
stat.close();
com.close();
}
执行结果如下:
非预编译批量提交 insert 5000条:
test2_1执行时间:9781
test2_1执行时间:10137
预编译批量提交 insert 5000条:(不加useServerPrepStmts参数,不开启SQL预编译)
test2_2执行时间:96
test2_2执行时间:93
预编译批量提交 insert 5000条:(加useServerPrepStmts参数,开启SQL预编译)
test2_2执行时间:173
test2_2执行时间:154
结论:MYSQL预编译批量提交insert语句比非预编译性能提升很高。非预编译的批量提交比单条提交(编译/非预编译)性能稍微有所提升。
上面结果有个问题,为何非预编译的批量提交,不开启预编译性能反而比开启预编译性能更高呢?按照常理,不开启预编译,就相当于是非预编译批量提交,性能不应该比开启预编译性能高呀。带着这个疑问,查看了MYSQL日志文件。发现不开启SQL预编译时,MYSQL把PrepareStatement的insert语句拼接成了insert … values(),()…()格式的多数据插入。而开启预编译功能后,MYSQL会先prepare预编译一下insert的sql,然后再执行insert语句。这样效率反而慢了。因此,在MYSQL的insert中,insert…values(),()…()效率是最高的。
update语句
代码如下:
/**
* 预编译与非预编译update语句比较---阈值
*/
public static void test5_1() throws Exception{
Class.forName("com.mysql.cj.jdbc.Driver");
System.out.println(000);
Connection com = DriverManager.getConnection("jdbc:mysql://localhost:3306/demo?rewriteBatchedStatements=true", "root", "AsDf!123");
System.out.println(666);
// DriverManager 注册驱动
// Connection 数据库连接对象 url(指定连接的路径 语法:“jdbc:mysql://ip地址:端口号/数据库名称”)
Statement stat = com.createStatement();
//执行 sql 语句的对象
System.out.println(111);
for(int i=0;i<5000;i++){
String sql="UPDATE `demo`.`ceshi`\n" +
" set `name` = '张三"+i+"',\n" +
" `age` = '18"+i+"',\n" +
" `sex` = '男"+i+"',\n" +
" `school` = '测试测试"+i+"',\n" +
" `birth` = '2023-03-07',\n" +
" `money` = '10.22',\n" +
" `createtime` = '2023-03-07 23:05:11',\n" +
" `remark` = '测试11111111测试222222222测试'\n" +
"WHERE\n" +
"\t(`id` = '"+(141646+i)+"')";
stat.addBatch(sql);
}
System.out.println(222);
long start=System.currentTimeMillis();
stat.executeBatch();
System.out.println("第一次test5_1执行时间:"+(System.currentTimeMillis()-start));
stat.clearBatch();
for(int i=0;i<5000;i++){
String sql="UPDATE `demo`.`ceshi`\n" +
" set `name` = '张三"+i+"',\n" +
" `age` = '18"+i+"',\n" +
" `sex` = '男"+i+"',\n" +
" `school` = '测试测试"+i+"',\n" +
" `birth` = '2023-03-07',\n" +
" `money` = '10.22',\n" +
" `createtime` = '2023-03-07 23:05:11',\n" +
" `remark` = '测试11111111测试222222222测试'\n" +
"WHERE\n" +
"\t(`id` = '"+(160646+i)+"')";
stat.addBatch(sql);
}
System.out.println(222);
start=System.currentTimeMillis();
stat.executeBatch();
System.out.println("第二次test5_1执行时间:"+(System.currentTimeMillis()-start));
stat.clearBatch();
for(int i=0;i<5000;i++){
String sql="UPDATE `demo`.`ceshi`\n" +
" set `name` = '张三"+i+"',\n" +
" `age` = '18"+i+"',\n" +
" `sex` = '男"+i+"',\n" +
" `school` = '测试测试"+i+"',\n" +
" `birth` = '2023-03-07',\n" +
" `money` = '10.22',\n" +
" `createtime` = '2023-03-07 23:05:11',\n" +
" `remark` = '测试11111111测试222222222测试'\n" +
"WHERE\n" +
"\t(`id` = '"+(182316+i)+"')";
stat.addBatch(sql);
}
System.out.println(222);
start=System.currentTimeMillis();
stat.executeBatch();
System.out.println("第三次test5_1执行时间:"+(System.currentTimeMillis()-start));
com.close();
stat.close();
com.close();
}
public static void test5_2() throws Exception{//&useServerPrepStmts=true&cachePrepStmts=true
Class.forName("com.mysql.cj.jdbc.Driver");
System.out.println(000);
Connection com = DriverManager.getConnection("jdbc:mysql://localhost:3306/demo?rewriteBatchedStatements=true", "root", "AsDf!123");
System.out.println(111);
// DriverManager 注册驱动
// Connection 数据库连接对象 url(指定连接的路径 语法:“jdbc:mysql://ip地址:端口号/数据库名称”)
String sql="UPDATE ceshi\n" +
"SET \n" +
" `name` = ?," +
" `age` = ?," +
" `sex` = ?," +
" `school` = ?," +
" `birth` = ?," +
" `money` = ?," +
" `createtime` = ?," +
" `remark` = ?" +
" WHERE\n" +
"\t`id` = ?";
PreparedStatement stat = com.prepareStatement(sql);
//执行 sql 语句的对象
for(int i=0;i<5000;i++){
stat.setObject(1,"张三"+i);
stat.setObject(2,"191"+i);
stat.setObject(3,"1男1"+i);
stat.setObject(4,"测试ceshi1"+i);
stat.setObject(5,"2023-03-07");
stat.setObject(6,"10.2211");
stat.setObject(7,"2023-03-07 23:05:11");
stat.setObject(8,"测试11111111测试222222222测试"+i);
stat.setObject(9,189305+i);
stat.addBatch();
}
long start=System.currentTimeMillis();
stat.executeBatch();
System.out.println("第一次test5_2执行时间:"+(System.currentTimeMillis()-start));
stat.clearBatch();
//执行 sql 语句的对象
for(int i=0;i<5000;i++){
stat.setObject(1,"张三"+i);
stat.setObject(2,"191"+i);
stat.setObject(3,"1男1"+i);
stat.setObject(4,"测试ceshi1"+i);
stat.setObject(5,"2023-03-07");
stat.setObject(6,"10.2211");
stat.setObject(7,"2023-03-07 23:05:11");
stat.setObject(8,"测试11111111测试222222222测试"+i);
stat.setObject(9,402287+i);
stat.addBatch();
}
start=System.currentTimeMillis();
stat.executeBatch();
System.out.println("第二次test5_2执行时间:"+(System.currentTimeMillis()-start));
stat.clearBatch();
//执行 sql 语句的对象
for(int i=0;i<5000;i++){
stat.setObject(1,"张三"+i);
stat.setObject(2,"191"+i);
stat.setObject(3,"1男1"+i);
stat.setObject(4,"测试ceshi1"+i);
stat.setObject(5,"2023-03-07");
stat.setObject(6,"10.2211");
stat.setObject(7,"2023-03-07 23:05:11");
stat.setObject(8,"测试11111111测试222222222测试"+i);
stat.setObject(9,602287+i);
stat.addBatch();
}
start=System.currentTimeMillis();
stat.executeBatch();
System.out.println("第三次test5_2执行时间:"+(System.currentTimeMillis()-start));
stat.clearBatch();
com.close();
stat.close();
com.close();
}
/**
* 预编译与非预编译单条提交 update语句测试
*/
public static void test6_1() throws Exception{
Class.forName("com.mysql.cj.jdbc.Driver");
System.out.println(000);
Connection com = DriverManager.getConnection("jdbc:mysql://localhost:3306/demo?rewriteBatchedStatements=true", "root", "AsDf!123");
System.out.println(666);
// DriverManager 注册驱动
// Connection 数据库连接对象 url(指定连接的路径 语法:“jdbc:mysql://ip地址:端口号/数据库名称”)
Statement stat = com.createStatement();
long start=System.currentTimeMillis();
//执行 sql 语句的对象
for(int i=0;i<5000;i++){
// long ss=System.currentTimeMillis();
String sql="UPDATE `demo`.`ceshi`\n" +
" set `name` = '张三"+i+"',\n" +
" `age` = '18"+i+"',\n" +
" `sex` = '男"+i+"',\n" +
" `school` = '测试测试"+i+"',\n" +
" `birth` = '2023-03-07',\n" +
" `money` = '10.22',\n" +
" `createtime` = '2023-03-07 23:05:11',\n" +
" `remark` = '测试11111111测试222222222测试'\n" +
"WHERE\n" +
"\t(`id` = '"+(12426+i)+"')";
stat.execute(sql);
//System.out.println("test6_1第"+i+"次执行时间:"+(System.currentTimeMillis()-ss));
}
System.out.println("总共test6_1执行时间:"+(System.currentTimeMillis()-start));
com.close();
stat.close();
com.close();
}
public static void test6_2() throws Exception{ //&useServerPrepStmts=true
Class.forName("com.mysql.cj.jdbc.Driver");
System.out.println(000);
Connection com = DriverManager.getConnection("jdbc:mysql://localhost:3306/demo?rewriteBatchedStatements=true", "root", "AsDf!123");
System.out.println(111);
// DriverManager 注册驱动
// Connection 数据库连接对象 url(指定连接的路径 语法:“jdbc:mysql://ip地址:端口号/数据库名称”)
String sql="UPDATE ceshi\n" +
"SET \n" +
" `name` = ?," +
" `age` = ?," +
" `sex` = ?," +
" `school` = ?," +
" `birth` = ?," +
" `money` = ?," +
" `createtime` = ?," +
" `remark` = ?" +
" WHERE\n" +
"\t`id` = ?";
PreparedStatement stat = com.prepareStatement(sql);
//执行 sql 语句的对象
long start=System.currentTimeMillis();
for(int i=0;i<5000;i++){
// long ss=System.currentTimeMillis();
stat.setObject(1,"张三"+i);
stat.setObject(2,"191"+i);
stat.setObject(3,"1男1"+i);
stat.setObject(4,"测试ceshi1"+i);
stat.setObject(5,"2023-03-07");
stat.setObject(6,"10.2211");
stat.setObject(7,"2023-03-07 23:05:11");
stat.setObject(8,"测试11111111测试222222222测试"+i);
stat.setObject(9,567299+i);
stat.execute();
// System.out.println("test6_2第"+i+"次执行时间:"+(System.currentTimeMillis()-ss));
}
System.out.println("第一次test6_2执行时间:"+(System.currentTimeMillis()-start));
com.close();
stat.close();
com.close();
}
执行结果如下:
非预编译单条update提交,5000条:
总共test6_1执行时间:10292
总共test6_1执行时间:10947
预编译单条update提交,5000条:
第一次test6_2执行时间:11026
第一次test6_2执行时间:13048
非预编译批量提交update 5000条:
第一次test5_1执行时间:9051
第二次test5_1执行时间:7818
第三次test5_1执行时间:6747
预编译批量提交update 5000条:
第一次test5_2执行时间:11259
第二次test5_2执行时间:11099
第三次test5_2执行时间:10489
结论:无论是单条提交还是批量提交,预编译和非预编译性能都差不多。批量提交性能稍微好于单条提交。对于MYSQL的update而言,预编译不能提升性能。批量提交稍稍能提高性能。
delete语句
代码如下:
/**
* 删除语句 批量提交 预编译与非预编译比较
*/
public static void test7_1() throws Exception{
Class.forName("com.mysql.cj.jdbc.Driver");
System.out.println(000);
Connection com = DriverManager.getConnection("jdbc:mysql://localhost:3306/demo?rewriteBatchedStatements=true&useServerPrepStmts=true&cachePrepStmts=true", "root", "AsDf!123");
System.out.println(666);
com.setAutoCommit(false);
// DriverManager 注册驱动
// Connection 数据库连接对象 url(指定连接的路径 语法:“jdbc:mysql://ip地址:端口号/数据库名称”)
Statement stat = com.createStatement();
long start=System.currentTimeMillis();
//执行 sql 语句的对象
for(int i=0;i<10000;i++){
// long ss=System.currentTimeMillis();
String sql="DELETE from `demo`.`ceshi`\n" +
"WHERE\n" +
"\t(`id` = '"+(11150+i)+"')";
stat.addBatch(sql);
//System.out.println("test6_1第"+i+"次执行时间:"+(System.currentTimeMillis()-ss));
}
stat.executeBatch();
com.commit();
System.out.println("总共test7_1执行时间:"+(System.currentTimeMillis()-start));
com.close();
stat.close();
com.close();
}
public static void test7_2() throws Exception{
Class.forName("com.mysql.cj.jdbc.Driver");
System.out.println(000);
Connection com = DriverManager.getConnection("jdbc:mysql://localhost:3306/demo?rewriteBatchedStatements=true&useServerPrepStmts=true&cachePrepStmts=true", "root", "AsDf!123");
System.out.println(111);
com.setAutoCommit(false);
// DriverManager 注册驱动
// Connection 数据库连接对象 url(指定连接的路径 语法:“jdbc:mysql://ip地址:端口号/数据库名称”)
String sql="delete from ceshi\n" +
" WHERE\n" +
"\t`id` = ?";
PreparedStatement stat = com.prepareStatement(sql);
//执行 sql 语句的对象
long start=System.currentTimeMillis();
for(int i=0;i<10000;i++){
// long ss=System.currentTimeMillis();
stat.setObject(1,32260+i);
stat.addBatch();
// System.out.println("test6_2第"+i+"次执行时间:"+(System.currentTimeMillis()-ss));
}
stat.executeBatch();
com.commit();
System.out.println("第一次test7_2执行时间:"+(System.currentTimeMillis()-start));
com.close();
stat.close();
com.close();
}
执行结果如下:
单条提交,提交100条:
总共test7_1执行时间:4116 (非预编译)
第一次test7_2执行时间:4303 (预编译)
批量提交,提交100条:
总共test7_1执行时间:164 (非预编译)
第一次test7_2执行时间:158 (预编译)
结论:预编译不会提升delete性能,批量提交会提升性能。
MYSQL结论
对于MYSQL而言,预编译提升SQL性能只能在insert语句提升明显。但是大批量提升insert性能最优的是insert…values(),()…()语句,预编译也不是首选。在update语句中,预编译不会提升SQL性能。批量提交会提升一些执行性能。在MYSQL中,insert…values(),()…()和批量delete性能较快,update语句执行性能也很慢。
最终结论
批量提交和SQL预编译在Oracle数据库和Mysql数据库中实现差异较大。
在Oracle中,预编译对SQL执行性能提升较大。在预编译的前提下,批量提交会进一步提升性能。
在Mysql中,预编译对SQL执行性能提升没影响,可能只是为了防止SQL注入。insert…values(),()…()插入性能最佳。update和delete批量提交可以提升性能。