1.數(shù)據(jù)庫預(yù)編譯起源
(1)數(shù)據(jù)庫SQL語句編譯特性:
數(shù)據(jù)庫接受到sql語句之后砰粹,需要詞法和語義解析蛛淋,優(yōu)化sql語句咙好,制定執(zhí)行計劃。這需要花費一些時間褐荷。但是很多情況勾效,我們的一條sql語句可能會反復(fù)執(zhí)行,或者每次執(zhí)行的時候只有個別的值不同
(比如query的where子句值不同叛甫,update的set子句值不同,insert的values值不同)层宫。
(2)減少編譯的方法
如果每次都需要經(jīng)過上面的詞法語義解析、語句優(yōu)化其监、制定執(zhí)行計劃等萌腿,則效率就明顯不行了。為了解決上面的問題棠赛,于是就有了預(yù)編譯哮奇,預(yù)編譯語句就是將這類語句中的值用占位符替代
,可以視為將sql語句模板化或者說參數(shù)化
睛约。一次編譯、多次運(yùn)行哲身,省去了解析優(yōu)化等過程辩涝。
(3)緩存預(yù)編譯
預(yù)編譯語句被DB的編譯器編譯后的執(zhí)行代碼被緩存下來,那么下次調(diào)用時只要是相同的預(yù)編譯語句就不需要編譯,只要將參數(shù)直接傳入編譯過的語句執(zhí)行代碼中(相當(dāng)于一個涵數(shù))就會得到執(zhí)行。
并不是所以預(yù)編譯語句都一定會被緩存
,數(shù)據(jù)庫本身會用一種策略(內(nèi)部機(jī)制)勘天。
(4) 預(yù)編譯的實現(xiàn)方法
預(yù)編譯是通過PreparedStatement和占位符來實現(xiàn)的怔揩。
2.預(yù)編譯作用:
- 預(yù)編譯階段可以優(yōu)化 sql 的執(zhí)行
預(yù)編譯之后的 sql 多數(shù)情況下可以直接執(zhí)行,DBMS 不需要再次編譯脯丝,越復(fù)雜的sql商膊,編譯的復(fù)雜度將越大,預(yù)編譯階段可以合并多次操作為一個操作宠进≡尾穑可以提升性能。 - 防止SQL注入
使用預(yù)編譯材蹬,而其后注入的參數(shù)將不會再進(jìn)行SQL編譯
实幕。也就是說其后注入進(jìn)來的參數(shù)系統(tǒng)將不會認(rèn)為它會是一條SQL語句,而默認(rèn)其是一個參數(shù)
堤器,參數(shù)中的or或者and 等就不是SQL語法保留字了昆庇。
3.預(yù)編譯開啟
(1)數(shù)據(jù)庫是否默認(rèn)開啟預(yù)編譯和JDBC版本有關(guān)。
也可以配置jdbc鏈接時強(qiáng)制開啟預(yù)編譯和緩存:useServerPrepStmts和cachePrepStmts參數(shù)闸溃。預(yù)編譯和預(yù)編譯緩存一定要同時開啟或同時關(guān)閉整吆。否則會影響執(zhí)行效率
conn = DriverManager.getConnection("jdbc:mysql://localhost:3306/prepare_stmt_test?user=root&password=root&useServerPrepStmts=true&cachePrepStmts=true");
(2)mysql的預(yù)編譯
- 開啟了預(yù)編譯緩存后拱撵,connection之間,預(yù)編譯的結(jié)果是獨立的表蝙,是無法共享的裕膀,一個connection無法得到另外一個connection的預(yù)編譯緩存結(jié)果。
- 經(jīng)過試驗勇哗,mysql的預(yù)編譯功能對性能影響不大昼扛,但在jdbc中使用PreparedStatement是必要的,可以有效地防止sql注入欲诺。
- 相同PreparedStatement的對象 抄谐,可以不用開啟預(yù)編譯緩存。
conn = DriverManager.getConnection("jdbc:mysql://localhost:3306/prepare_stmt_test?user=root&password=root&useServerPrepStmts=true");
PreparedStatement stmt = conn.prepareStatement(sql);
stmt.setString(1, "aaa");
ResultSet rs1 = stmt.executeQuery();//第一次執(zhí)行
s1.close();
stmt.setString(1, "ddd");
ResultSet rs2 = stmt.executeQuery();//第二次執(zhí)行
rs2.close();
stmt.close();
//查看mysql日志
1 Prepare select * from users where name = ?
1 Execute select * from users where name = 'aaa'
1 Execute select * from users where name = 'ddd'
4.mybatis是如何實現(xiàn)預(yù)編譯的
mybatis 默認(rèn)情況下扰法,將對所有的 sql 進(jìn)行預(yù)編譯蛹含。mybatis底層使用PreparedStatement,過程是先將帶有占位符(即”?”)的sql模板發(fā)送至mysql服務(wù)器塞颁,由服務(wù)器對此無參數(shù)的sql進(jìn)行編譯后浦箱,將編譯結(jié)果緩存,然后直接執(zhí)行帶有真實參數(shù)的sql祠锣。核心是通過#{ } 實現(xiàn)的
酷窥。
在預(yù)編譯之前,#{ } 解析為一個 JDBC 預(yù)編譯語句(prepared statement)的參數(shù)標(biāo)記符?伴网。
//sqlMap 中如下的 sql 語句
select * from user where name = #{name};
//解析成為預(yù)編譯語句
select * from user where name = ?;
如果${ }蓬推,SQL 解析階段將會進(jìn)行變量替換。不能實現(xiàn)預(yù)編譯澡腾。
select * from user where name = '${name}'
//傳遞的參數(shù)為 "ruhua" 時,解析為如下沸伏,然后發(fā)送數(shù)據(jù)庫服務(wù)器進(jìn)行編譯。
select * from user where name = "ruhua";