网易首页 > 网易号 > 正文 申请入驻

java培训 | Mybatis 中的 PreparedStatement 预编译

0
分享至

前言

大家都知道,Mybatis内置参数,形如#{xxx}的,均采用了sql预编译的形式,大致知道mybatis底层使用PreparedStatement,过程是先将带有占位符(即”?”)的sql模板发送至mysql服务器,由服务器对此无参数的sql进行编译后,将编译结果缓存,然后直接执行带有真实参数的sql。如果你的基本结论也是如此,那你就大错特错了。

1. mysql是否默认开启了预编译功能?

mysql是否支持预编译有两层意思:

  • db是否支持预编译
  • 连接数据库的url是否指定了需要预编译,比如:jdbc:mysql://127.0.0.1:3306/user?useServerPrepStmts=true,useServerPrepStmts=true是非常非常重要的参数。如果不配置PreparedStatement 实际是个假的 PreparedStatement

SELECT VERSION(); // 5.6.24-log

SHOW GLOBAL STATUS LIKE '%prepare%'; //Com_stmt_prepare 4 代表被执行预编译次数

//开启server日志

SHOW VARIABLES LIKE '%general_log%';

SHOW VARIABLES LIKE 'log_output';

SET GLOBAL general_log = ON;

SET GLOBAL log_output='table';

TRUNCATE TABLE mysql.general_log;

SELECT * FROM mysql.general_log; // 有Prepare命令

注意:mysql预编译功能有版本要求,包括server版本和mysql.jar包版本。以前的版本默认useServerPrepStmts=true,5.0.5以后的版本默认useServerPrepStmts=false

2. 预编译缓存是服务端还是客户端缓存?

开启缓存:useServerPrepStmts=true&cachePrepStmts=true,设置了useServerPrepStmts=true,虽然可以一次编译,多次执行

它可以提高性能,但缓存是针对连接的,即每个连接的缓存都是独立的,并且缓存主要是由mysql-connector-java.jar实现的。

当手动调用prepareStatement.close()时PrepareStatement对象只会将关闭状态置为关闭,并不会向mysql发送关闭请求,【关注尚硅谷,轻松学IT】prepareStatement对象会被缓存起来,等下次使用的时候直接从缓存中取出来使用。没有开启缓存,则会向mysql发送closeStmt的请求。

3. 开启预编译性能更高?

也就是说预编译比非预编译更好?其实不然,不行自己可试试看。

public class PreparedStatement_test {

private String url = "jdbc:mysql://localhost:3306/batch";

private String sql = "SELECT * FROM export_request WHERE id = ?";

private int maxTimes = 100000;

@Test

public void go_driver() throws SQLException, ClassNotFoundException {

Class.forName("com.mysql.jdbc.Driver");

Connection conn = (Connection) DriverManager.getConnection(url, "root", "123456");

// PreparedStatement

Stopwatch stopwatch = Stopwatch.createStarted();

for (int i = 0; i < maxTimes; i++) {

PreparedStatement stmt = conn.prepareStatement(sql);

stmt.setLong(1, Math.abs(new Random().nextLong()));

// execute

stmt.executeQuery();

System.out.println("go_driver:" + stopwatch);

@Test

public void go_setPre() throws SQLException, ClassNotFoundException {

Class.forName("com.mysql.jdbc.Driver");

Connection conn = (Connection) DriverManager.getConnection(url + "?useServerPrepStmts=true", "root", "123456");

// PreparedStatement

Stopwatch stopwatch = Stopwatch.createStarted();

for (int i = 0; i < maxTimes; i++) {

PreparedStatement stmt = conn.prepareStatement(sql);

stmt.setLong(1, Math.abs(new Random().nextLong()));

// execute

stmt.executeQuery();

System.out.println("go_setPre:" + stopwatch);

@Test

public void go_setPreCache() throws SQLException, ClassNotFoundException {

Class.forName("com.mysql.jdbc.Driver");

Connection conn = (Connection) DriverManager.getConnection(url + "?useServerPrepStmts=true&cachePrepStmts=true", "root", "123456");

// PreparedStatement

PreparedStatement stmt = conn.prepareStatement(sql);

stmt.setLong(1, Math.abs(new Random().nextLong()));

// execute

stmt.executeQuery();

stmt.close();//非常重要的,一定要调用才会缓存

Stopwatch stopwatch = Stopwatch.createStarted();

for (int i = 0; i < maxTimes; i++) {

stmt = conn.prepareStatement(sql);

stmt.setLong(1, Math.abs(new Random().nextLong()));

// execute

stmt.executeQuery();

System.out.println("go_setPreCache:" + stopwatch);

基准为10w次单线程:

  • 非预编译::23.78 s
  • 预编译:41.86 s
  • 预编译缓存:20.55 s

经过实践测试,对于频繁适用的语句,使用预编译+缓存确实能够得到可观的提升,但对于不频繁适用的语句,服务端编译会增加额外的round-trip。开发实践中要视情况而定。

4. 从源码中验证

预编译原理(connection -> prepareStatement )

预编译:JDBC42ServerPreparedStatement(需将对应占位符)

非预编译:JDBC42PreparedStatement(完整的SQL)

//com.mysql.jdbc.ConnectionImpl中的代码片段

* JDBC 2.0 Same as prepareStatement() above, but allows the default result

* set type and result set concurrency type to be overridden.

* @param sql

* the SQL query containing place holders

* @param resultSetType

* a result set type, see ResultSet.TYPE_XXX

* @param resultSetConcurrency

* a concurrency type, see ResultSet.CONCUR_XXX

* @return a new PreparedStatement object containing the pre-compiled SQL

* statement

* @exception SQLException

* if a database-access error occurs.

public java.sql.PreparedStatement prepareStatement(String sql, int resultSetType, int resultSetConcurrency) throws SQLException {

synchronized (getConnectionMutex()) {

checkClosed();

// FIXME: Create warnings if can't create results of the given type or concurrency

//当Client开启 useServerPreparedStmts 并且Server支持 ServerPrepare

PreparedStatement pStmt = null;

boolean canServerPrepare = true;

String nativeSql = getProcessEscapeCodesForPrepStmts() ? nativeSQL(sql) : sql;

if (this.useServerPreparedStmts && getEmulateUnsupportedPstmts()) {

canServerPrepare = canHandleAsServerPreparedStatement(nativeSql);

if (this.useServerPreparedStmts && canServerPrepare) {// 从缓存中获取 pStmt

if (this.getCachePreparedStatements()) {

synchronized (this.serverSideStatementCache) {

pStmt = (com.mysql.jdbc.ServerPreparedStatement) this.serverSideStatementCache

.remove(makePreparedStatementCacheKey(this.database, sql));

if (pStmt != null) {

((com.mysql.jdbc.ServerPreparedStatement) pStmt).setClosed(false);

pStmt.clearParameters();// 清理上次留下的参数

if (pStmt == null) {

try {// 向Server提交 SQL 预编译,实例是JDBC42ServerPreparedStatement

pStmt = ServerPreparedStatement.getInstance(getMultiHostSafeProxy(), nativeSql, this.database, resultSetType,

resultSetConcurrency);

if (sql.length() < getPreparedStatementCacheSqlLimit()) {

((com.mysql.jdbc.ServerPreparedStatement) pStmt).isCached = true;

pStmt.setResultSetType(resultSetType);

pStmt.setResultSetConcurrency(resultSetConcurrency);

} catch (SQLException sqlEx) {

// Punt, if necessary

if (getEmulateUnsupportedPstmts()) {

pStmt = (PreparedStatement) clientPrepareStatement(nativeSql, resultSetType, resultSetConcurrency, false);

if (sql.length() < getPreparedStatementCacheSqlLimit()) {

this.serverSideStatementCheckCache.put(sql, Boolean.FALSE);

} else {

throw sqlEx;

} else {

try { // 向Server提交 SQL 预编译。

pStmt = ServerPreparedStatement.getInstance(getMultiHostSafeProxy(), nativeSql, this.database, resultSetType, resultSetConcurrency);

pStmt.setResultSetType(resultSetType);

pStmt.setResultSetConcurrency(resultSetConcurrency);

} catch (SQLException sqlEx) {

// Punt, if necessary

if (getEmulateUnsupportedPstmts()) {

pStmt = (PreparedStatement) clientPrepareStatement(nativeSql, resultSetType, resultSetConcurrency, false);

} else {

throw sqlEx;

} else {// Server不支持 ServerPrepare,实例是JDBC42PreparedStatement

pStmt = (PreparedStatement) clientPrepareStatement(nativeSql, resultSetType, resultSetConcurrency, false);

return pStmt;

JDBC42ServerPreparedStatement->close,缓存

//com.mysql.jdbc.ServerPreparedStatement中选取代码

@Override

public void close() throws SQLException {

MySQLConnection locallyScopedConn = this.connection;

if (locallyScopedConn == null) {

return; // already closed

synchronized (locallyScopedConn.getConnectionMutex()) {

if (this.isCached && isPoolable() && !this.isClosed) {

clearParameters();// 若开启缓存,则只会将状态位设为已关闭,并且刷新缓存

this.isClosed = true;

this.connection.recachePreparedStatement(this);

return;

//没有开启缓存,则会向mysql发送closeStmt的请求

realClose(true, true);

public void recachePreparedStatement(ServerPreparedStatement pstmt) throws SQLException {

synchronized (getConnectionMutex()) {

if (getCachePreparedStatements() && pstmt.isPoolable()) {

synchronized (this.serverSideStatementCache) {

Object oldServerPrepStmt = this.serverSideStatementCache.put(makePreparedStatementCacheKey(pstmt.currentCatalog, pstmt.originalSql), pstmt);

if (oldServerPrepStmt != null) {// 将sql语句作为key,reparedStatement对象作为value存放到缓存中

((ServerPreparedStatement) oldServerPrepStmt).isCached = false;

((ServerPreparedStatement) oldServerPrepStmt).realClose(true, true);

5. 总结

  • 预编译显式开启(在url中指定useServerPrepStmts=true),否则PreparedStatement不会向mysql发送预编译(Prepare命令)的请求;
  • 每次向mysql发送预编译请求,不管之前有没有执行过此SQL语句,只要请求的命令是Prepare或Query,mysql就会重新编译一次SQL语句,并返回此链接当前唯一的Statement ID,后续执行SQL语句的时候,程序只需拿着Statement ID和参数就可以了;
  • 当预编译的SQL语句有语法错误,则mysql的响应会携带错误信息,但此错误信息JDBC感知不到(或者说mysql-connetor-java.jar包里的实现将其忽略掉了),此时还会继续往下执行代码,www.atguigu.com当执行到executeXxx()方法时,由于没有Statement ID(所以就会将拼接完整的SQL语句值已经将占位符(?)替换掉再次发给mysql请求执行,此时mysql响应有语法错误,这时JDBC就会抛出语法错误异常),所以检查语法那一步实在mysql-server中做的(通过抓包可以看到);
  • PreparedStatement对性能的提高是利用缓存实现的,需要显式开启(在url中指定cachePrepStmts=true),此缓存是mysql-connetor-java.jar包里实现的(非mysql-server中的缓存),缓存的key是完整的sql语句,value是PreparedStatement对象。放入缓存是PreparedStatement.close()触发的,所以只要缓存PreparedStatement对象没有关闭,你不管调用多少次connection.prapareStatement(sql)对相同的sql语句进行预编译,都会将预编译的请求发给mysql,mysql也会对每一个sql语句不管是否相同进行预编译,并生成一个唯一的Statement ID并返回;
  • 缓存是针对链接的,每个链接都是独立的,不共享缓存

文章来源于Java知音

特别声明:以上内容(如有图片或视频亦包括在内)为自媒体平台“网易号”用户上传并发布,本平台仅提供信息存储服务。

Notice: The content above (including the pictures and videos if any) is uploaded and posted by a user of NetEase Hao, which is a social media platform and only provides information storage services.

相关推荐
热点推荐
詹俊:克罗地亚本场漏洞百出,莫德里奇没老而布罗佐维奇老了

詹俊:克罗地亚本场漏洞百出,莫德里奇没老而布罗佐维奇老了

直播吧
2024-06-19 23:25:22
刺激!美洲杯除决赛以外的淘汰赛没有加时赛,打平直接进点球大战

刺激!美洲杯除决赛以外的淘汰赛没有加时赛,打平直接进点球大战

直播吧
2024-06-20 13:49:09
王思聪女儿疑似蝴蝶手,一岁多还不会说话,黄一鸣回应惹争议!

王思聪女儿疑似蝴蝶手,一岁多还不会说话,黄一鸣回应惹争议!

古希腊掌管月桂的神
2024-06-19 16:50:48
张艺谋首部电视剧《主角》3选1:赵丽颖刘亦菲孙俪,5方面考察?

张艺谋首部电视剧《主角》3选1:赵丽颖刘亦菲孙俪,5方面考察?

小路杂谈
2024-06-18 16:27:54
前中国香港队主帅安德森:国足早晚可以凭借实力杀进世界杯

前中国香港队主帅安德森:国足早晚可以凭借实力杀进世界杯

直播吧
2024-06-20 11:41:11
中国女排巴黎奥运名单公布:朱婷领衔,张常宁、李盈莹在列

中国女排巴黎奥运名单公布:朱婷领衔,张常宁、李盈莹在列

懂球帝
2024-06-19 17:12:16
马蓉刚走又来了冯清,倒霉的王宝强,终究还是绕不过“女人坑”

马蓉刚走又来了冯清,倒霉的王宝强,终究还是绕不过“女人坑”

娱乐白名单
2024-06-13 10:25:31
单亲妈35岁下海当女优 15岁儿子看的性感写真:别再变的出名了

单亲妈35岁下海当女优 15岁儿子看的性感写真:别再变的出名了

楚门记
2024-06-20 09:14:08
笑死!韩国招标无人机,没想到中标的设计竟然是从我们网上抄的

笑死!韩国招标无人机,没想到中标的设计竟然是从我们网上抄的

笔墨V
2024-06-20 08:46:18
两列火车相撞,已致15死60伤!

两列火车相撞,已致15死60伤!

应急360
2024-06-19 10:11:56
事闹大了,南方医科大领导召开紧急会议,俞莉医生疑似被停职。

事闹大了,南方医科大领导召开紧急会议,俞莉医生疑似被停职。

文雅笔墨
2024-06-18 01:43:02
杨幂最新写真,极致腰臀比!!

杨幂最新写真,极致腰臀比!!

风子说个球
2024-06-19 17:28:55
黄一鸣小羽联手嘲讽,王思聪玩鹰被鹰啄,黄一鸣:下次连孙一宁

黄一鸣小羽联手嘲讽,王思聪玩鹰被鹰啄,黄一鸣:下次连孙一宁

米椒娱乐
2024-06-19 12:12:04
央视暗访“小升初”招生:教育从畸形走向变态,是“双减”后最大的悲哀

央视暗访“小升初”招生:教育从畸形走向变态,是“双减”后最大的悲哀

每周文摘
2024-06-18 11:17:48
“母亲借钱买的”电瓶车不合标准被没收,女孩哭得撕心裂肺!

“母亲借钱买的”电瓶车不合标准被没收,女孩哭得撕心裂肺!

走读新生
2024-06-15 07:25:14
中国女排与同组对手交手纪录:对塞尔维亚11胜9负,对美国9胜11负

中国女排与同组对手交手纪录:对塞尔维亚11胜9负,对美国9胜11负

直播吧
2024-06-19 19:52:51
涉嫌严重违纪违法!佛山一国企总经理被查

涉嫌严重违纪违法!佛山一国企总经理被查

南方都市报
2024-06-20 11:38:19
6月18日俄乌:各方愈发强硬,满足俄罗斯损失更多的期望

6月18日俄乌:各方愈发强硬,满足俄罗斯损失更多的期望

山河路口
2024-06-18 17:20:38
是骡子是马拉出来遛遛:天才中专生姜萍被疑作弊,数学月考仅85分

是骡子是马拉出来遛遛:天才中专生姜萍被疑作弊,数学月考仅85分

瑜说还休
2024-06-17 12:19:02
美国想在日韩部署核武?俄霸气质问美国:是不是这样,直接回答我

美国想在日韩部署核武?俄霸气质问美国:是不是这样,直接回答我

博览历史
2024-06-17 19:55:31
2024-06-20 14:04:49
IT爱好者小尚
IT爱好者小尚
分享IT教育类信息
630文章数 55关注度
往期回顾 全部

科技要闻

苹果回应AI仅限iPhone15Pro:不是为卖新机

头条要闻

34岁德国老将贡献大师级表现 曾称要在欧洲杯后退役

头条要闻

34岁德国老将贡献大师级表现 曾称要在欧洲杯后退役

体育要闻

绿军的真老大,开始备战下赛季了

娱乐要闻

离谱!24岁女偶像参加涉毒男星生日聚会,坐在桌边陪赌

财经要闻

日本银行巨头突然爆雷!

汽车要闻

售价11.79-14.39万元 新一代哈弗H6正式上市

态度原创

家居
数码
艺术
公开课
军事航空

家居要闻

自然开放 实现灵动可变空间

数码要闻

泄露的图片展示了三星Galaxy Watch Ultra以及正在充电的Galaxy Ring

艺术要闻

穿越时空的艺术:《马可·波罗》AI沉浸影片探索人类文明

公开课

近视只是视力差?小心并发症

军事要闻

普京再送金正恩轿车 两人轮流当司机

无障碍浏览 进入关怀版