轉(zhuǎn)載于JDBC:深入理解PreparedStatement和Statement
主要優(yōu)化排版與閱讀
前言
最近聽(tīng)一個(gè)老師講了公開(kāi)課锄奢,在其中講到了 PreparedStatement 的執(zhí)行原理和 Statement 的區(qū)別。
當(dāng)時(shí)聽(tīng)公開(kāi)課老師講的時(shí)候感覺(jué)以前就只知道 PreparedStatement 是 “預(yù)編譯類”痒蓬,能夠?qū)?sql 語(yǔ)句進(jìn)行預(yù)編譯晨继,預(yù)編譯后能夠提高數(shù)據(jù)庫(kù) sql 語(yǔ)句執(zhí)行效率蜂大。
但是,聽(tīng)了那個(gè)老師講后我就突然很想問(wèn)自己源譬,預(yù)編譯千贯??是誰(shuí)對(duì) sql 語(yǔ)句的預(yù)編譯晌坤?逢艘?是數(shù)據(jù)庫(kù)?還是 PreparedStatement 對(duì)象骤菠?它改?到底什么是預(yù)編譯?商乎?為什么能夠提高效率央拖??為什么在數(shù)據(jù)庫(kù)操作時(shí)能夠防止 sql 注入攻擊鹉戚?鲜戒?這就引起了我對(duì) Preparedstatement 的疑惑。
公開(kāi)課老師講的時(shí)候說(shuō):”PreparedStatement 會(huì)對(duì) sql 文進(jìn)行預(yù)編譯抹凳,預(yù)編譯后遏餐,會(huì)存儲(chǔ)在 PreparedStatement 對(duì)象中,等下次再執(zhí)行這個(gè) PreparedStatement 對(duì)象時(shí)赢底,會(huì)提高很多效率”失都。這句話我聽(tīng)了后更疑惑了,預(yù)編譯是什么我不知道就算了幸冻,竟然還說(shuō):對(duì) sql 預(yù)編譯后會(huì)存儲(chǔ)在 PreparedStatement 對(duì)象中粹庞??我就想問(wèn)問(wèn) sql 預(yù)編譯后是什么洽损?庞溜?什么被存儲(chǔ)在 PreparedStatement 對(duì)象中?碑定?
更讓人感覺(jué)疑惑的是 Statement流码。對(duì)就是 Statement,公開(kāi)課老師說(shuō):“同一條 sql 語(yǔ)句(字符串都是相同的)在 Statement 對(duì)象中多次執(zhí)行時(shí)延刘,Statement 只會(huì)對(duì)當(dāng)前 sql 文編譯一次旅掂,編譯后存儲(chǔ)在 Statement 中,在之后的執(zhí)行過(guò)程中访娶,都不會(huì)進(jìn)行編譯而是直接運(yùn)行 sql 語(yǔ)句”商虐。什么?崖疤?我沒(méi)聽(tīng)錯(cuò)吧秘车?Statement 還有編譯?劫哼?等等等等叮趴。。权烧。眯亦。我當(dāng)時(shí)真的是聽(tīng)的懷疑人生伤溉。
PreparedStatement
在說(shuō) PreparedStatement 之前,我們來(lái)看看什么是預(yù)編譯妻率。其實(shí)預(yù)編譯是 MySQL 數(shù)據(jù)庫(kù)本身都支持的乱顾。但是 MySQL Server 4.1 之前的版本是不支持預(yù)編譯的。(具體是否包括 4.1 還得讀者們親自試驗(yàn))
在這里宫静,筆者用的是 MySQL5.6 綠色版走净。
MySQL 中的預(yù)編譯功能是這樣的
預(yù)編譯的好處:
大家平時(shí)都使用過(guò) JDBC 中的 PreparedStatement 接口,它有預(yù)編譯功能孤里。什么是預(yù)編譯功能呢伏伯?它有什么好處呢?
當(dāng)客戶發(fā)送一條 SQL 語(yǔ)句給服務(wù)器后捌袜,服務(wù)器總是需要校驗(yàn) SQL 語(yǔ)句的語(yǔ)法格式是否正確说搅,然后把 SQL 語(yǔ)句編譯成可執(zhí)行的函數(shù),最后才是執(zhí)行 SQL 語(yǔ)句虏等。其中校驗(yàn)語(yǔ)法蜓堕,和編譯所花的時(shí)間可能比執(zhí)行 SQL 語(yǔ)句花的時(shí)間還要多。
注意:可執(zhí)行函數(shù)存儲(chǔ)在 MySQL 服務(wù)器中博其,并且當(dāng)前連接斷開(kāi)后套才,MySQL 服務(wù)器會(huì)清除已經(jīng)存儲(chǔ)的可執(zhí)行函數(shù)。
如果我們需要執(zhí)行多次 insert 語(yǔ)句慕淡,但只是每次插入的值不同背伴,MySQL 服務(wù)器也是需要每次都去校驗(yàn) SQL 語(yǔ)句的語(yǔ)法格式,以及編譯峰髓,這就浪費(fèi)了太多的時(shí)間傻寂。如果使用預(yù)編譯功能,那么只對(duì) SQL 語(yǔ)句進(jìn)行一次語(yǔ)法校驗(yàn)和編譯携兵,所以效率要高疾掰。
MySQL 執(zhí)行預(yù)編譯
MySQL 執(zhí)行預(yù)編譯分為如三步:
- 執(zhí)行預(yù)編譯語(yǔ)句,例如:prepare showUsersByLikeName from 'select * from user where username like ?';
- 設(shè)置變量徐紧,例如:set @username='% 小明 %';
- 執(zhí)行語(yǔ)句静檬,例如:execute showUsersByLikeName using @username;
如果需要再次執(zhí)行 myfun,那么就不再需要第一步并级,即不需要再編譯語(yǔ)句了:
- 設(shè)置變量拂檩,例如:set @username='% 小宋 %';
- 執(zhí)行語(yǔ)句,例如:execute showUsersByLikeName using @username;
如果你看 MySQL 日志記錄嘲碧,你就會(huì)看到:
配置 MySQL 日志記錄
路徑地址可以自己修改稻励。
log-output=FILE
general-log=1
general_log_file="E:\mysql.log"
slow-query-log=1
slow_query_log_file="E:\mysql_slow.log"
long_query_time=2
配置之后就重啟 MySQL 服務(wù)器:
在 cmd 管理員界面執(zhí)行以下操作。
net stop mysql
net start mysql
使用 PreparedStatement 執(zhí)行 sql 查詢
JDBC MySQL 驅(qū)動(dòng) 5.0.5 以后的版本默認(rèn) PreparedStatement 是關(guān)閉預(yù)編譯功能的愈涩,所以需要我們手動(dòng)開(kāi)啟望抽。而之前的 JDBC MySQL 驅(qū)動(dòng)版本默認(rèn)是開(kāi)啟預(yù)編譯功能的加矛。
MySQL 數(shù)據(jù)庫(kù)服務(wù)器的預(yù)編譯功能在 4.1 之后才支持預(yù)編譯功能的。如果數(shù)據(jù)庫(kù)服務(wù)器不支持預(yù)編譯功能時(shí)煤篙,并且使用 PreparedStatement 開(kāi)啟預(yù)編譯功能是會(huì)拋出異常的斟览。這點(diǎn)非常重要。筆者用的是 mysql-connector-jar-5.1.13 版本的 JDBC 驅(qū)動(dòng)舰蟆。
在我們以前寫(xiě)項(xiàng)目的時(shí)候,貌似都沒(méi)有注意是否開(kāi)啟 PreparedStatement 的預(yù)編譯功能狸棍,以為它一直都是在使用的身害,現(xiàn)在看看不開(kāi)啟 PreparedStatement 的預(yù)編譯,查看 MySQL 的日志輸出到底是怎么樣的草戈。
@Test
public void showUser(){
//數(shù)據(jù)庫(kù)連接
Connection connection = null;
//預(yù)編譯的Statement塌鸯,使用預(yù)編譯的Statement提高數(shù)據(jù)庫(kù)性能
PreparedStatement preparedStatement = null;
//結(jié)果 集
ResultSet resultSet = null;
try {
//加載數(shù)據(jù)庫(kù)驅(qū)動(dòng)
Class.forName("com.mysql.jdbc.Driver");
//通過(guò)驅(qū)動(dòng)管理類獲取數(shù)據(jù)庫(kù)鏈接
connection = DriverManager.getConnection("jdbc:mysql://localhost:3306/mybatis", "root", "");
//定義sql語(yǔ)句 ?表示占位符
String sql = "select * from user where username = ?";
//獲取預(yù)處理statement
preparedStatement = connection.prepareStatement(sql);
//設(shè)置參數(shù),第一個(gè)參數(shù)為sql語(yǔ)句中參數(shù)的序號(hào)(從1開(kāi)始)唐片,第二個(gè)參數(shù)為設(shè)置的參數(shù)值
preparedStatement.setString(1, "王五");
//向數(shù)據(jù)庫(kù)發(fā)出sql執(zhí)行查詢丙猬,查詢出結(jié)果集
resultSet = preparedStatement.executeQuery();
preparedStatement.setString(1, "張三");
resultSet = preparedStatement.executeQuery();
//遍歷查詢結(jié)果集
while(resultSet.next()){
System.out.println(resultSet.getString("id")+" "+resultSet.getString("username"));
}
resultSet.close();
preparedStatement.close();
System.out.println("#############################");
} catch (Exception e) {
e.printStackTrace();
}finally{
//釋放資源
if(resultSet!=null){
try {
resultSet.close();
} catch (SQLException e) {
}
}
if(preparedStatement!=null){
try {
preparedStatement.close();
} catch (SQLException e) {
}
}
if(connection!=null){
try {
connection.close();
} catch (SQLException e) {
}
}
}
}
這是輸出日志:
Query /* mysql-connector-java-5.1.13 ( Revision: ${bzr.revision-id} ) */
SELECT @@session.auto_increment_increment
Query SHOW COLLATION
Query SET NAMES utf8mb4
Query SET character_set_results = NULL
Query SET autocommit=1
Query select * from user where username = '王五'
Query select * from user where username = '張三'
Quit
可以看到,在日志中并沒(méi)有看到 "prepare" 命令來(lái)預(yù)編譯 "select * from user where username = ?" 這個(gè) sql 模板费韭。所以我們一般用的 PreparedStatement 并沒(méi)有用到預(yù)編譯功能的茧球,只是用到了防止 sql 注入攻擊的功能。防止 sql 注入攻擊的實(shí)現(xiàn)是在 PreparedStatement 中實(shí)現(xiàn)的星持,和服務(wù)器無(wú)關(guān)抢埋。筆者在源碼中看到,PreparedStatement 對(duì)敏感字符已經(jīng)轉(zhuǎn)義過(guò)了督暂。
在 PreparedStatement 中開(kāi)啟預(yù)編譯功能
設(shè)置 MySQL 連接 URL 參數(shù):useServerPrepStmts=true揪垄,如下所示。
jdbc:mysql://localhost:3306/mybatis?&useServerPrepStmts=true
這樣才能保證 mysql 驅(qū)動(dòng)會(huì)先把 SQL 語(yǔ)句發(fā)送給服務(wù)器進(jìn)行預(yù)編譯逻翁,然后在執(zhí)行 executeQuery() 時(shí)只是把參數(shù)發(fā)送給服務(wù)器饥努。
再次執(zhí)行上面的程序看下 MySQL 日志輸出:
Query SHOW WARNINGS
Query /* mysql-connector-java-5.1.13 ( Revision: ${bzr.revision-id} ) */
SELECT @@session.auto_increment_increment
Query SHOW COLLATION
Query SET NAMES utf8mb4
Query SET character_set_results = NULL
Query SET autocommit=1
Prepare select * from user where username = ?
Execute select * from user where username = '王五'
Execute select * from user where username = '張三'
Close stmt
Quit
很明顯已經(jīng)進(jìn)行了預(yù)編譯,Prepare select * from user where username = ?八回,這一句就是對(duì) sql 語(yǔ)句模板進(jìn)行預(yù)編譯的日志酷愧。好的非常 Nice。
注意:
我們?cè)O(shè)置的是 MySQL 連接參數(shù)缠诅,目的是告訴 MySQL JDBC 的 PreparedStatement 使用預(yù)編譯功能(5.0.5 之后的 JDBC 驅(qū)動(dòng)版本需要手動(dòng)開(kāi)啟伟墙,而之前的默認(rèn)是開(kāi)啟的),不管我們是否使用預(yù)編譯功能滴铅,MySQL Server4.1 版本以后都是支持預(yù)編譯功能的戳葵。
cachePrepStmts 參數(shù)
當(dāng)使用不同的 PreparedStatement 對(duì)象來(lái)執(zhí)行相同的 SQL 語(yǔ)句時(shí),還是會(huì)出現(xiàn)編譯兩次的現(xiàn)象汉匙,這是因?yàn)轵?qū)動(dòng)沒(méi)有緩存編譯后的函數(shù) key拱烁,導(dǎo)致二次編譯生蚁。如果希望緩存編譯后函數(shù)的 key,那么就要設(shè)置 cachePrepStmts 參數(shù)為 true戏自。例如:
jdbc:mysql://localhost:3306/mybatis?useServerPrepStmts=true&cachePrepStmts=true
程序代碼:
@Test
public void showUser(){
//數(shù)據(jù)庫(kù)連接
Connection connection = null;
//預(yù)編譯的Statement邦投,使用預(yù)編譯的Statement提高數(shù)據(jù)庫(kù)性能
PreparedStatement preparedStatement = null;
//結(jié)果 集
ResultSet resultSet = null;
try {
//加載數(shù)據(jù)庫(kù)驅(qū)動(dòng)
Class.forName("com.mysql.jdbc.Driver");
//通過(guò)驅(qū)動(dòng)管理類獲取數(shù)據(jù)庫(kù)鏈接
connection = DriverManager.getConnection("jdbc:mysql://localhost:3306/mybatis?&useServerPrepStmts=true&cachePrepStmts=true", "root", "");
preparedStatement=connection.prepareStatement("select * from user where username like ?");
preparedStatement.setString(1, "%小明%");
resultSet = preparedStatement.executeQuery();
//遍歷查詢結(jié)果集
while(resultSet.next()){
System.out.println(resultSet.getString("id")+" "+resultSet.getString("username"));
}
//注意這里必須要關(guān)閉當(dāng)前PreparedStatement對(duì)象流,否則下次再次創(chuàng)建PreparedStatement對(duì)象的時(shí)候還是會(huì)再次預(yù)編譯sql模板擅笔,使用PreparedStatement對(duì)象后不關(guān)閉當(dāng)前PreparedStatement對(duì)象流是不會(huì)緩存預(yù)編譯后的函數(shù)key的
resultSet.close();
preparedStatement.close();
preparedStatement=connection.prepareStatement("select * from user where username like ?");
preparedStatement.setString(1, "%三%");
resultSet = preparedStatement.executeQuery();
//遍歷查詢結(jié)果集
while(resultSet.next()){
System.out.println(resultSet.getString("id")+" "+resultSet.getString("username"));
}
resultSet.close();
preparedStatement.close();
} catch (Exception e) {
e.printStackTrace();
}finally{
//釋放資源
if(resultSet!=null){
try {
resultSet.close();
} catch (SQLException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
if(preparedStatement!=null){
try {
preparedStatement.close();
} catch (SQLException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
if(connection!=null){
try {
connection.close();
} catch (SQLException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
}
}
日志輸出:
Query SHOW WARNINGS
Query /* mysql-connector-java-5.1.13 ( Revision: ${bzr.revision-id} ) */
SELECT @@session.auto_increment_increment
Query SHOW COLLATION
Query SET NAMES utf8mb4
Query SET character_set_results = NULL
Query SET autocommit=1
Prepare select * from user where username like ?
Execute select * from user where username like '%小明%'
Execute select * from user where username like '%三%'
Quit
注意:每次使用 PreparedStatement 對(duì)象后都要關(guān)閉該 PreparedStatement 對(duì)象流志衣,否則預(yù)編譯后的函數(shù) key 是不會(huì)緩存的。
Statement 執(zhí)行 sql 語(yǔ)句是否會(huì)對(duì)編譯后的函數(shù)進(jìn)行緩存
這個(gè)不好說(shuō)猛们,對(duì)于每個(gè)數(shù)據(jù)庫(kù)的具體實(shí)現(xiàn)都是不一樣的念脯,對(duì)于預(yù)編譯肯定都大體相同,但是對(duì)于 Statement 和普通 sql弯淘,數(shù)據(jù)庫(kù)一般都是先檢查 sql 語(yǔ)句是否正確绿店,然后編譯 sql 語(yǔ)句成為函數(shù),最后執(zhí)行函數(shù)庐橙。其實(shí)也不乏某些數(shù)據(jù)庫(kù)很瘋狂假勿,對(duì)于普通 sql 的函數(shù)進(jìn)行緩存。但是目前的主流數(shù)據(jù)庫(kù)都不會(huì)對(duì) sql 函數(shù)進(jìn)行緩存的态鳖。因?yàn)?sql 語(yǔ)句變化那么多转培,如果對(duì)所有函數(shù)緩存,那么對(duì)于內(nèi)存的消耗也是非常巨大的浆竭。
如果你不確定普通 sql 語(yǔ)句的函數(shù)是否被存儲(chǔ)堡距,那要怎么做呢?兆蕉?
其實(shí)還是一個(gè)道理羽戒,查看 MySQL 日志記錄:檢查第二次執(zhí)行相同 sql 語(yǔ)句時(shí),是否是直接通過(guò) execute 來(lái)進(jìn)行查詢的虎韵。
public void showUser() {
//數(shù)據(jù)庫(kù)連接
Connection connection = null;
//預(yù)編譯的Statement易稠,使用預(yù)編譯的Statement提高數(shù)據(jù)庫(kù)性能
PreparedStatement preparedStatement = null;
//結(jié)果 集
ResultSet resultSet = null;
try {
//加載數(shù)據(jù)庫(kù)驅(qū)動(dòng)
Class.forName("com.mysql.jdbc.Driver");
//通過(guò)驅(qū)動(dòng)管理類獲取數(shù)據(jù)庫(kù)鏈接
connection = DriverManager.getConnection("jdbc:mysql://localhost:3306/mybatis?&useServerPrepStmts=true&cachePrepStmts=true", "root", "");
Statement statement = connection.createStatement();
resultSet = statement.executeQuery("select * from user where user);
//遍歷查詢結(jié)果集
while (resultSet.next()) {
System.out.println(resultSet.getString("id") + " " + resultSet.getString("username"));
}
resultSet.close();
statement.close();
statement = connection.createStatement();
resultSet = statement.executeQuery("select * from user where user);
//遍歷查詢結(jié)果集
while (resultSet.next()) {
System.out.println(resultSet.getString("id") + " " + resultSet.getString("username"));
}
resultSet.close();
statement.close();
} catch (Exception e) {
e.printStackTrace();
} finally {
//釋放資源
if (resultSet != null) {
try {
resultSet.close();
} catch (SQLException e) {
}
}
if (preparedStatement != null) {
try {
preparedStatement.close();
} catch (SQLException e) {
}
}
if (connection != null) {
try {
connection.close();
} catch (SQLException e) {
}
}
}
}
日志記錄:
Query SHOW WARNINGS
Query /* mysql-connector-java-5.1.13 ( Revision: ${bzr.revision-id} ) */SELECT @@session.auto_increment_increment
Query SHOW COLLATION
Query SET NAMES utf8mb4
Query SET character_set_results = NULL
Query SET autocommit=1
Query select * from user where username='小天'
Query select * from user where username='小天'
Quit
看日志就會(huì)知道,都是 Query 命令包蓝,所以并沒(méi)有存儲(chǔ)函數(shù)驶社。
總結(jié):
所以到了這里我的疑惑都解開(kāi)了,PreparedStatement 的預(yù)編譯是數(shù)據(jù)庫(kù)進(jìn)行的测萎,編譯后的函數(shù) key 是緩存在 PreparedStatement 中的亡电,編譯后的函數(shù)是緩存在數(shù)據(jù)庫(kù)服務(wù)器中的。預(yù)編譯前有檢查 sql 語(yǔ)句語(yǔ)法是否正確的操作硅瞧。只有數(shù)據(jù)庫(kù)服務(wù)器支持預(yù)編譯功能時(shí)份乒,JDBC 驅(qū)動(dòng)才能夠使用數(shù)據(jù)庫(kù)的預(yù)編譯功能,否則會(huì)報(bào)錯(cuò)。預(yù)編譯在比較新的 JDBC 驅(qū)動(dòng)版本中默認(rèn)是關(guān)閉的或辖,需要配置連接參數(shù)才能夠打開(kāi)瘾英。在已經(jīng)配置好了數(shù)據(jù)庫(kù)連接參數(shù)的情況下,Statement 對(duì)于 MySQL 數(shù)據(jù)庫(kù)是不會(huì)對(duì)編譯后的函數(shù)進(jìn)行緩存的颂暇,數(shù)據(jù)庫(kù)不會(huì)緩存函數(shù)缺谴,Statement 也不會(huì)緩存函數(shù)的 key,所以多次執(zhí)行相同的一條 sql 語(yǔ)句的時(shí)候耳鸯,還是會(huì)先檢查 sql 語(yǔ)句語(yǔ)法是否正確湿蛔,然后編譯 sql 語(yǔ)句成函數(shù),最后執(zhí)行函數(shù)县爬。
對(duì)于 PreparedStatement 在設(shè)置參數(shù)的時(shí)候會(huì)對(duì)參數(shù)進(jìn)行轉(zhuǎn)義處理阳啥。
因?yàn)?PreparedStatement 已經(jīng)對(duì) sql 模板進(jìn)行了編譯,并且存儲(chǔ)了函數(shù)捌省,所以 PreparedStatement 做的就是把參數(shù)進(jìn)行轉(zhuǎn)義后直接傳入?yún)?shù)到數(shù)據(jù)庫(kù)苫纤,然后讓函數(shù)執(zhí)行碉钠。這就是為什么 PreparedStatement 能夠防止 sql 注入攻擊的原因了纲缓。
PreparedStatement 的預(yù)編譯還有注意的問(wèn)題,在數(shù)據(jù)庫(kù)端存儲(chǔ)的函數(shù)和在 PreparedStatement 中存儲(chǔ)的 key 值喊废,都是建立在數(shù)據(jù)庫(kù)連接的基礎(chǔ)上的祝高,如果當(dāng)前數(shù)據(jù)庫(kù)連接斷開(kāi)了,數(shù)據(jù)庫(kù)端的函數(shù)會(huì)清空污筷,建立在連接上的 PreparedStatement 里面的函數(shù) key 也會(huì)被清空工闺,各個(gè)連接之間的預(yù)編譯都是互相獨(dú)立的。
使用 Statement 執(zhí)行預(yù)編譯
使用 Statement 執(zhí)行預(yù)編譯就是把上面的原始 SQL 語(yǔ)句預(yù)編譯執(zhí)行一次瓣蛀。
Connection con = JdbcUtils.getConnection();
Statement stmt = con.createStatement();
stmt.executeUpdate("prepare myfun from'select * from t_book where bid=?'");
stmt.executeUpdate("set @str='b1'");
ResultSet rs = stmt.executeQuery("execute myfun using @str");
while (rs.next()) {
System.out.print(rs.getString(1) + ",");
System.out.print(rs.getString(2) + ",");
System.out.print(rs.getString(3) + ",");
System.out.println(rs.getString(4));
}
stmt.executeUpdate("set @str='b2'");
rs = stmt.executeQuery("execute myfun using @str");
while (rs.next()) {
System.out.print(rs.getString(1) + ", ");
System.out.print(rs.getString(2) + ", ");
System.out.print(rs.getString(3) + ", ");
System.out.println(rs.getString(4));
}
rs.close();
stmt.close();
con.close();
在持久層框架中存在的問(wèn)題
很多主流持久層框架 (MyBatis陆蟆,Hibernate) 其實(shí)都沒(méi)有真正的用上預(yù)編譯,預(yù)編譯是要我們自己在參數(shù)列表上面配置的惋增,如果我們不手動(dòng)開(kāi)啟叠殷,JDBC 驅(qū)動(dòng)程序 5.0.5 以后版本 默認(rèn)預(yù)編譯都是關(guān)閉的。
所以我們要在參數(shù)列表中配置诈皿,例如:
jdbc:mysql://localhost:3306/mybatis?&useServerPrepStmts=true&cachePrepStmts=true
注意:
在 MySQL 中林束,既要開(kāi)啟預(yù)編譯也要開(kāi)啟緩存。因?yàn)槿绻皇情_(kāi)啟預(yù)編譯的話效率還沒(méi)有不開(kāi)啟預(yù)編譯效率高稽亏,大家可以做一下性能測(cè)試壶冒,其中性能測(cè)試結(jié)果在這篇博客中有寫(xiě)到,探究 mysql 預(yù)編譯截歉,而在 MySQL 中開(kāi)啟預(yù)編譯和開(kāi)啟緩存胖腾,其中的查詢效率和不開(kāi)啟預(yù)編譯和不開(kāi)啟緩存的效率是持平的。這里用的測(cè)試類是 PreparedStatement。
參考資料:
探究 mysql 預(yù)編譯
PreparedStatement 是如何大幅度提高性能的
參考中文文檔下載:MySQL 預(yù)編譯功能