预编译
当客户端发送一条sql语句给DBMS时,MySQL的执行流程如下图
SQL命令的执行流程如下:
-
客户端向服务器端发送SQL命令 -
服务器端连接模块连接并验证 -
缓存模块解析SQL为Hash并与缓存中Hash表对应。如果有结果直接返回结果,如果没有对应继续向下执行 -
解析器解析SQL为解析树,如果出现错误,报SQL解析错误。如果正确,向下传递 -
预处理器对解析树继续处理,处理成新的解析树。 -
优化器根据开销自动选择最优执行计划,生成执行计划 -
执行器执行执行计划,访问存储引擎接口 -
存储引擎访问物理文件并返回结果 -
如果开启缓存,缓存管理器把结果放入到查询缓存中。 -
返回结果给客户端
当客户发送一条SQL语句给DBMS后,DBMS总是需要校验SQL语句的语法格式是否正确,然后把SQL语句编译成可执行的函数,最后才是执行SQL语句。其中校验语法,和编译所花的时间可能比执行SQL语句花的时间还要多。
预编译语句PreparedStatement 是java.sql中的一个接口,它是Statement的子接口。通过Statement对象执行SQL语句时,需要将SQL语句发送给DBMS,由DBMS首先进行编译后再执行。预编译语句和Statement不同,在创建PreparedStatement 对象时就指定了SQL语句,该语句立即发送给DBMS进行编译。当该编译语句被执行时,DBMS直接运行编译后的SQL语句,而不需要像其他SQL语句那样首先将其编译。预编译的SQL语句处理性能稍微高于普通的传递变量的办法。
例如:我们需要执行多次insert语句,但只是每次插入的值不同,MySQL服务器也是需要每次都去校验SQL语句的语法格式,以及编译,这就浪费了太多的时间。如果使用预编译功能,那么只对SQL语句进行一次语法校验和编译,所以效率要高。
开启预编译:
我们可以通过设置URL中的参数来控制预编译是否开启
"jdbc:mysql://localhost:3306/mydb?*****&useServerPrepStmts=true&cachePrepStmts=true";
值得注意的是,我们的Connector/J 5.0.5及之后useServerPrepStmts默认false,就是默认没有开启预编译,之前默认为true, cachePrepStmts 一直默认为false,需要我们手动设置才可以启用预编译,在开启预编译的同时要同时开启预编译缓存才能带来些许的性能提升
Statement和PreparedStatment的关系和区别
关系:PreparedStatement 接口继承 Statement 接口
区别:
-
PreparedStatment安全性高,可以避免SQL注入 -
PreparedStatment简单不繁琐,不用进行字符串拼接 -
PreparedStatment性能高,用在执行多个相同数据库DML操作时,可以减少sql语句的编译次数
PreparedStatement 批处理
批处理含义:
当我们有多条sql语句需要发送到数据库执行的时候,有两种发送方式,一种是执行一条发送一条sql语句给数据库,另一个种是发送一个sql集合给数据库,也就是发送一个批sql到数据库。普通的执行过程是:每处理一条数据,就访问一次数据库;而批处理是:累积到一定数量,再一次性提交到数据库,减少了与数据库的交互次数,所以效率会大大提高,很显然两者的数据库执行效率是不同的,我们发送批处理sql的时候数据库执行效率要高
statement和PreparedStatement实现批处理比较:
statement: 优点:采用硬编码效率低,安全性较差。 原理:硬编码,每次执行时相似SQL都会进行编译
PreparedStatement 优点:语句只编译一次,减少编译次数。提高了安全性(阻止了SQL注入) 原理:相似SQL只编译一次,减少编译次数
开启批处理:&rewriteBatchedStatements=true
public class TestBatch {
private static String driver ="com.mysql.cj.jdbc.Driver";
private static String url="jdbc:mysql://127.0.0.1:3306/mydb?useSSL=false&useUnicode=true&characterEncoding=UTF-8&serverTimezone=Asia/Shanghai&allowPublicKeyRetrieval=true&useServerPrepStmts=true&cachePrepStmts=true&&rewriteBatchedStatements=true";
private static String user="root";
private static String password="root";
public static void main(String[] args) {
testAddBatch();
}
public static void testAddBatch(){
Connection connection = null;
PreparedStatement preparedStatement=null;
try{
Class.forName(driver);
connection = DriverManager.getConnection(url, user,password);
String sql="insert into dept values (DEFAULT ,?,?)";
preparedStatement = connection.prepareStatement(sql);
for (int i = 1; i <= 10663; i++) {
preparedStatement.setString(1, "name");
preparedStatement.setString(2, "loc");
preparedStatement.addBatch();
if(i%1000==0){
preparedStatement.executeBatch();
preparedStatement.clearBatch();
}
}
preparedStatement.executeBatch();
preparedStatement.clearBatch();
}catch (Exception e){
e.printStackTrace();
}finally {
if(null != preparedStatement){
try {
preparedStatement.close();
} catch (SQLException e) {
e.printStackTrace();
}
}
if(null != connection){
try {
connection.close();
} catch (SQLException e) {
e.printStackTrace();
}
}
}
}
}
|